From 055e23ff04274df5c99de3b243b6bad0d64c873c Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 27 Jan 2025 12:05:17 +0100 Subject: [PATCH 01/25] add some logic Signed-off-by: alperozturk --- app/build.gradle | 13 +- .../share/CreateShareViaLinkOperation.java | 91 +++ .../notes/share/NoteShareFragment.java | 647 ++++++++++++++++++ .../owncloud/notes/share/SyncOperation.java | 37 + .../owncloud/notes/shared/server/Server.kt | 40 ++ .../owncloud/notes/shared/user/User.kt | 56 ++ .../notes/shared/util/ClipboardUtil.kt | 50 ++ app/src/main/res/values/strings.xml | 127 +--- gradle/verification-metadata.xml | 62 +- 9 files changed, 976 insertions(+), 147 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt diff --git a/app/build.gradle b/app/build.gradle index 088e7628a..c18c1a1cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,17 +95,12 @@ ext { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' - implementation 'com.google.guava:guava:31.1-android' - implementation ('commons-httpclient:commons-httpclient:3.1') { - exclude group: 'commons-logging', module: 'commons-logging' - } - - implementation("com.github.nextcloud:android-library:2.19.0") { + implementation("com.github.nextcloud:android-library:3d422b28376339c0fbd772e480dbbdc56b7ae1a1") { exclude group: 'org.ogce', module: 'xpp3' } // Nextcloud SSO - implementation 'com.github.nextcloud.android-common:ui:0.23.2' + implementation 'com.github.nextcloud.android-common:ui:0.24.0' implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.2' implementation 'com.github.stefan-niedermann:android-commons:1.0.2' implementation "com.github.stefan-niedermann.nextcloud-commons:sso-glide:$commonsVersion" @@ -122,7 +117,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation 'androidx.fragment:fragment:1.8.6' + implementation 'androidx.fragment:fragment:1.8.5' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7' implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0' @@ -139,7 +134,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.11.0' // Gson - implementation 'com.google.code.gson:gson:2.12.1' + implementation 'com.google.code.gson:gson:2.11.0' // ReactiveX implementation 'io.reactivex.rxjava2:rxjava:2.2.21' diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java new file mode 100644 index 000000000..509fce819 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java @@ -0,0 +1,91 @@ +package it.niedermann.owncloud.notes.share; + +import java.util.ArrayList; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.ShareType; + + +import it.niedermann.owncloud.notes.shared.user.User; + +/** + * Creates a new public share for a given file + */ +public class CreateShareViaLinkOperation extends SyncOperation { + + private final String path; + private final String password; + private int permissions = OCShare.NO_PERMISSION; + + public CreateShareViaLinkOperation(String path, String password, User user) { + super(user); + + this.path = path; + this.password = password; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path, + ShareType.PUBLIC_LINK, + "", + false, + password, + permissions); + createOp.setGetShareDetails(true); + RemoteOperationResult result = createOp.execute(client); + + if (result.isSuccess()) { + if (result.getData().size() > 0) { + Object item = result.getData().get(0); + if (item instanceof OCShare) { + updateData((OCShare) item); + } else { + ArrayList data = result.getData(); + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); + result.setData(data); + } + } else { + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); + } + } + + return result; + } + + private void updateData(OCShare share) { + // Update DB with the response + share.setPath(path); + if (path.endsWith(FileUtils.PATH_SEPARATOR)) { + share.setFolder(true); + } else { + share.setFolder(false); + } + + // TODO: Save share + + /* + getStorageManager().saveShare(share); + + // Update OCFile with data from share: ShareByLink and publicLink + OCFile file = getStorageManager().getFileByEncryptedRemotePath(path); + if (file != null) { + file.setSharedViaLink(true); + getStorageManager().saveFile(file); + } + */ + + } + + public String getPath() { + return this.path; + } + + public String getPassword() { + return this.password; + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java new file mode 100644 index 000000000..5f1004d38 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java @@ -0,0 +1,647 @@ +package it.niedermann.owncloud.notes.share; + +import android.Manifest; +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 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.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.List; + +import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; +import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.branding.BrandedSnackbar; +import it.niedermann.owncloud.notes.databinding.FragmentNoteShareBinding; +import it.niedermann.owncloud.notes.persistence.entity.Account; +import it.niedermann.owncloud.notes.persistence.entity.Note; +import it.niedermann.owncloud.notes.shared.user.User; +import it.niedermann.owncloud.notes.shared.util.ClipboardUtil; +import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; + +public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, + DisplayUtils.AvatarGenerationListener, + Injectable, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { + + private static final String TAG = "NoteShareFragment"; + private static final String ARG_NOTE = "NOTE"; + private static final String ARG_ACCOUNT = "ACCOUNT"; + private static final String ARG_USER = "USER"; + + private FragmentNoteShareBinding binding; + private Note note; + private User user; + private Account account; + + private OnEditShareListener onEditShareListener; + + public static NoteShareFragment newInstance(Note note, User user, Account account) { + NoteShareFragment fragment = new NoteShareFragment(); + Bundle args = new Bundle(); + args.putSerializable(ARG_NOTE, note); + args.putSerializable(ARG_ACCOUNT, account); + args.putParcelable(ARG_USER, user); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + note = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_NOTE, Note.class); + account = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_ACCOUNT, Account.class); + user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_USER, User.class); + } else { + Bundle arguments = getArguments(); + if (arguments != null) { + note = BundleExtensionsKt.getSerializableArgument(arguments, ARG_NOTE, Note.class); + account = BundleExtensionsKt.getSerializableArgument(arguments, ARG_ACCOUNT, Account.class); + user = BundleExtensionsKt.getParcelableArgument(arguments, ARG_USER, User.class); + } + } + + if (note == null) { + throw new IllegalArgumentException("Note cannot be null"); + } + + if (user == null) { + throw new IllegalArgumentException("Account cannot be null"); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + refreshCapabilitiesFromDB(); + refreshSharesFromDB(); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentNoteShareBinding.inflate(inflater, container, false); + + binding.sharesList.setAdapter(new ShareeListAdapter(fileActivity, + new ArrayList<>(), + this, + user, + note)); + + binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); + + binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); + + setupView(); + + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + 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 setupView() { + setShareWithYou(); + + OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + + FileDetailSharingFragmentHelper.setupSearchView( + (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), + binding.searchView, + fileActivity.getComponentName()); + viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); + + if (file.canReshare()) { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); + } else { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + } + } + + private void disableSearchView(View view) { + view.setEnabled(false); + + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + disableSearchView(viewGroup.getChildAt(i)); + } + } + } + + // TODO: Check if note.getAccountId() return note's owner's id + private boolean accountOwnsFile() { + String noteId = String.valueOf(note.getAccountId()); + return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); + } + + private void setShareWithYou() { + if (accountOwnsFile()) { + binding.sharedWithYouContainer.setVisibility(View.GONE); + } else { + + /* + // TODO: How to get owner display name from note? + + binding.sharedWithYouUsername.setText( + String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); + */ + + + Glide.with(this) + .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) + .placeholder(R.drawable.ic_account_circle_grey_24dp) + .error(R.drawable.ic_account_circle_grey_24dp) + .apply(RequestOptions.circleCropTransform()) + .into(binding.sharedWithYouAvatar); + + binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); + + /* + // TODO: Note's note? + String note = file.getNote(); + + if (!TextUtils.isEmpty(note)) { + binding.sharedWithYouNote.setText(file.getNote()); + binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); + } else { + binding.sharedWithYouNoteContainer.setVisibility(View.GONE); + } + */ + } + } + + private void copyInternalLink() { + if (account == null) { + BrandedSnackbar.make(requireView(), getString(R.string.note_share_fragment_could_not_retrieve_url), Snackbar.LENGTH_LONG) + .setAnchorView(binding.sharesList) + .show(); + return; + } + + showShareLinkDialog(); + } + + private void showShareLinkDialog() { + String link = createInternalLink(); + + Intent intentToShareLink = new Intent(Intent.ACTION_SEND); + + intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); + intentToShareLink.setType("text/plain"); + intentToShareLink.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.note_share_fragment_subject_shared_with_you, note.getTitle())); + + String[] packagesToExclude = new String[] { requireContext().getPackageName() }; + DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); + chooserDialog.show(getParentFragmentManager(), FileDisplayActivity.FTAG_CHOOSER_DIALOG); + } + + // TODO: Check account.getUrl returning base url? + private String createInternalLink() { + Uri baseUri = Uri.parse(account.getUrl()); + return baseUri + "/index.php/f/" + note.getId(); + } + + // TODO: Capabilities in notes app doesn't have following functions... + public void createPublicShareLink() { + if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); + + } else { + // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note + // Is CreateShareViaLink operation compatible? + + Intent service = new Intent(fileActivity, OperationsService.class); + service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); + service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); + if (!TextUtils.isEmpty(password)) { + service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); + } + service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); + } + } + + private void createSecureFileDrop() { + fileOperationsHelper.shareFolderViaSecureFileDrop(file); + } + + /* + // TODO: Cant call getFileWithLink + + public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { + List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + if (shares.size() == SINGLE_LINK_SIZE) { + FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); + } else { + if (fileActivity instanceof FileDisplayActivity) { + ((FileDisplayActivity) fileActivity).showDetails(file, 1); + } else { + showShareFile(file); + } + } + + fileActivity.refreshList(); + } + */ + private void showSendLinkTo(OCShare publicShare) { + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(publicShare.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + // TODO: get link from public share and pass to the function + showShareLinkDialog(); + } + } + } + + public void copyLink(OCShare share) { + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(share.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + } + } + } + + /** + * 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).show(); + } + } + + /** + * show quick sharing permission dialog + * + * @param share + */ + @Override + public void showPermissionsDialog(OCShare share) { + new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils).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) { + if (result.isSuccess()) { + refreshUiFromDB(); + } else { + setupView(); + } + } + + /** + * Get {@link OCShare} instance from DB and updates the UI. + */ + private void refreshUiFromDB() { + refreshSharesFromDB(); + // Updates UI with new state + setupView(); + } + + private void unshareWith(OCShare share) { + fileOperationsHelper.unshareShare(file, share); + } + + /** + * 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); + } + + @Override + public void requestPasswordForShare(OCShare share, boolean askForPassword) { + SharePasswordDialogFragment dialog = SharePasswordDialogFragment.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(); + } + } + + /** + * Get known server capabilities from DB + */ + public void refreshCapabilitiesFromDB() { + capabilities = fileDataStorageManager.getCapability(user.getAccountName()); + } + + /** + * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities + * before reading database. + */ + public void refreshSharesFromDB() { + OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); + if (newFile != null) { + file = newFile; + } + + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + + if (adapter == null) { + DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); + return; + } + adapter.getShares().clear(); + + // to show share with users/groups info + List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), + user.getAccountName()); + + adapter.addShares(shares); + + if (FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities) || !file.canReshare()) { + return; + } + + // Get public share + List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares()) && + (!file.isEncrypted() || capabilities.getEndToEndEncryption().isTrue())) { + final OCShare ocShare = new OCShare(); + ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); + publicShares.add(ocShare); + } else { + adapter.removeNewPublicShare(); + } + + adapter.addShares(publicShares); + } + + private void checkContactPermission() { + if (PermissionUtil.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + pickContactEmail(); + } else { + requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); + } + } + + private void pickContactEmail() { + Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); + + if (intent.resolveActivity(requireContext().getPackageManager()) != null) { + onContactSelectionResultLauncher.launch(intent); + } else { + DisplayUtils.showSnackMessage(requireActivity(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message)); + } + } + + private void handleContactResult(@NonNull Uri contactUri) { + // Define the projection to get all email addresses. + String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; + + Cursor cursor = fileActivity.getContentResolver().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); + 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(() -> { + binding.searchView.setQuery(email, false); + binding.searchView.requestFocus(); + }); + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address."); + } + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); + } + cursor.close(); + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); + } + } + + private boolean containsNoNewPublicShare(List shares) { + for (OCShare share : shares) { + if (share.getShareType() == ShareType.NEW_PUBLIC_LINK) { + return false; + } + } + + return true; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + FileExtensionsKt.logFileSize(file, TAG); + outState.putParcelable(ARG_NOTE, file); + outState.putParcelable(ARG_USER, user); + } + + @Override + public void avatarGenerated(Drawable avatarDrawable, Object callContext) { + binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); + } + + @Override + public boolean shouldCallGeneratedCallback(String tag, Object callContext) { + return false; + } + + private boolean isReshareForbidden(OCShare share) { + return ShareType.FEDERATED == share.getShareType() || + capabilities != null && capabilities.getFilesSharingResharing().isFalse(); + } + + @VisibleForTesting + public void search(String query) { + SearchView searchView = requireView().findViewById(R.id.searchView); + searchView.setQuery(query, true); + } + + @Override + public void advancedPermissions(OCShare share) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); + } + + + @Override + public void sendNewEmail(OCShare share) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); + } + + @Override + public void unShare(OCShare share) { + unshareWith(share); + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + if (adapter == null) { + DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui)); + return; + } + adapter.remove(share); + } + + @Override + public void sendLink(OCShare share) { + if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { + FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); + } else { + showSendLinkTo(share); + } + } + + @Override + public void addAnotherLink(OCShare share) { + createPublicShareLink(); + } + + private void modifyExistingShare(OCShare share, int screenTypePermission) { + onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), + capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); + } + + @Override + public void onQuickPermissionChanged(OCShare share, int permission) { + fileOperationsHelper.setPermissionsToShare(share, permission); + } + + //launcher for contact permission + private final ActivityResultLauncher requestContactPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + pickContactEmail(); + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.contact_no_permission); + } + }); + + //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(binding.getRoot(), R.string.email_pick_failed); + return; + } + + Uri contactUri = intent.getData(); + if (contactUri == null) { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + return; + } + + handleContactResult(contactUri); + + } + }); + + public interface OnEditShareListener { + void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown, + boolean isExpiryDateShown); + + void onShareProcessClosed(); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java new file mode 100644 index 000000000..62eb8661a --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java @@ -0,0 +1,37 @@ +package it.niedermann.owncloud.notes.share; + + +import android.content.Context; +import android.os.Handler; + +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.common.User; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; + +import androidx.annotation.NonNull; + +public abstract class SyncOperation extends RemoteOperation { + private final User user; + + public SyncOperation(@NonNull User user) { + this.user = user; + } + + public RemoteOperationResult execute(Context context) { + return super.execute(user, context); + } + + public RemoteOperationResult execute(@NonNull NextcloudClient client) { + return run(client); + } + + public Thread execute(OwnCloudClient client, + OnRemoteOperationListener listener, + Handler listenerHandler) { + return super.execute(client, listener, listenerHandler); + } + +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt new file mode 100644 index 000000000..be773c7f7 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt @@ -0,0 +1,40 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2019 Chris Narkiewicz + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package it.niedermann.owncloud.notes.shared.server + +import android.os.Parcel +import android.os.Parcelable +import com.owncloud.android.lib.resources.status.OwnCloudVersion +import java.net.URI + +/** + * This object provides all information necessary to interact + * with backend server. + */ +data class Server(val uri: URI, val version: OwnCloudVersion) : Parcelable { + + constructor(source: Parcel) : this( + source.readSerializable() as URI, + source.readParcelable(OwnCloudVersion::class.java.classLoader) as OwnCloudVersion + ) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { + writeSerializable(uri) + writeParcelable(version, 0) + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): Server = Server(source) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt new file mode 100644 index 000000000..b68c4598d --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt @@ -0,0 +1,56 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2020 Chris Narkiewicz + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package it.niedermann.owncloud.notes.shared.user + +import android.accounts.Account +import android.os.Parcelable +import com.owncloud.android.lib.common.OwnCloudAccount +import it.niedermann.owncloud.notes.shared.server.Server + +interface User : Parcelable, com.nextcloud.common.User { + override val accountName: String + val server: Server + + /** + * This is temporary helper method created to facilitate incremental refactoring. + * Code using legacy platform Account can be partially converted to instantiate User + * object and use account instance when required. + * + * This method calls will allow tracing code awaiting further refactoring. + * + * @return Account instance that is associated with this User object. + */ + @Deprecated("Temporary workaround") + override fun toPlatformAccount(): Account + + /** + * This is temporary helper method created to facilitate incremental refactoring. + * Code using legacy ownCloud account can be partially converted to instantiate User + * object and use account instance when required. + * + * This method calls will allow tracing code awaiting further refactoring. + * + * @return OwnCloudAccount instance that is associated with this User object. + */ + @Deprecated("Temporary workaround") + fun toOwnCloudAccount(): OwnCloudAccount + + /** + * Compare account names, case insensitive. + * + * @return true if account names are same, false otherwise + */ + fun nameEquals(user: User?): Boolean + + /** + * Compare account names, case insensitive. + * + * @return true if account names are same, false otherwise + */ + fun nameEquals(accountName: CharSequence?): Boolean +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt new file mode 100644 index 000000000..cb6c5e14f --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt @@ -0,0 +1,50 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2018 Andy Scherzinger + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ + +package it.niedermann.owncloud.notes.shared.util + +import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.text.TextUtils +import android.widget.Toast +import com.owncloud.android.lib.common.utils.Log_OC +import it.niedermann.owncloud.notes.R + +/** + * Helper implementation to copy a string into the system clipboard. + */ +object ClipboardUtil { + private val TAG = ClipboardUtil::class.java.name + + @JvmStatic + @JvmOverloads + @Suppress("TooGenericExceptionCaught") + fun copyToClipboard(activity: Activity, text: String?, showToast: Boolean = true) { + if (!TextUtils.isEmpty(text)) { + try { + val clip = ClipData.newPlainText( + activity.getString( + R.string.clipboard_label, + activity.getString(R.string.app_name) + ), + text + ) + (activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(clip) + if (showToast) { + Toast.makeText(activity, R.string.clipboard_text_copied, Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(activity, R.string.clipboard_unexpected_error, Toast.LENGTH_SHORT).show() + Log_OC.e(TAG, "Exception caught while copying to clipboard", e) + } + } else { + Toast.makeText(activity, R.string.clipboard_no_text_to_copy, Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6341244b..06af4e559 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,122 +38,17 @@ Choose a category - - com.nextcloud.android.providers.UsersAndGroupsSearchProvider - com.nextcloud.android.providers.UsersAndGroupsSearchProvider.action.SHARE_WITH - - - - @string/link_share_view_only - @string/link_share_allow_upload_and_editing - @string/link_share_file_drop - - - @string/link_share_view_only - @string/link_share_editing - - - - note activity icon - note share icon - note share external icon - note share copy icon - note share more icon - note share icon - note share user icon - note share contact icon - Shared with you by %1$s - Share note - Name, Federated Cloud ID or email address… - Share link - Policy or permissions prevent resharing - Could not retrieve URL - \"%1$s\" has been shared with you - Contact permission is required. - - - Advanced Settings - Hide download - Note to recipient - Note - Next - Share and Copy Link - Confirm - Set Note - Send Share - Name - Link Name - You must enter a password - Password - Please select at least one permission to share. - Label cannot be empty - Cancel - Failed to create a share - - - Enter an optional password - Skip - - Enter a password - OK - Delete - Send email - No app available to handle links - No App available to handle mail address - No actions for this user - - Failed to remove share - Failed to pick email address. - Failed to update UI - No app available to select contacts - Send link to… - - Send - Internal share link only works for users with access to this folder - Internal share link only works for users with access to this file - Share internal link - Delete Link - Settings - Send new email - Sharing - Share %1$s - Expires %1$s - %1$s - Set expiration date - Share link - Send link - Password-protected - Set password - Share with… - Unset - Add another link - Add new public share link - New name - Share link (%1$s) - Share link - Allow resharing - View only - Editing - Allow upload and editing - File drop (upload only) - Could not retrieve shares - View only - Can edit - File drop - Secure file drop - Share Permissions - - %1$d download remaining - %1$d downloads remaining - - Username - %1$s (group) - %1$s (remote) - %1$s (conversation) - on %1$s - (remote) - - + note share copy icon + note share more icon + note share icon + note share user icon + note share contact icon + Shared with you by %1$s + Name, Federated Cloud ID or email address… + Share link + Policy or permissions prevent resharing + Could not retrieve URL + \"%1$s\" has been shared with you Copy link Link copied diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b942a25eb..3b295593a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -9,7 +9,6 @@ - @@ -107,6 +106,7 @@ + @@ -855,6 +855,11 @@ + + + + + @@ -1185,14 +1190,6 @@ - - - - - - - - @@ -1206,14 +1203,6 @@ - - - - - - - - @@ -5051,12 +5040,12 @@ - - - + + + - - + + @@ -5155,6 +5144,14 @@ + + + + + + + + @@ -5251,6 +5248,14 @@ + + + + + + + + @@ -5347,6 +5352,14 @@ + + + + + + + + @@ -5915,6 +5928,11 @@ + + + + + From 370c4bd9b28c52991c655b63c7e4874df09d63d4 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 28 Jan 2025 10:06:51 +0100 Subject: [PATCH 02/25] first successfull build Signed-off-by: alperozturk --- app/build.gradle | 9 +- .../notes/share/ClientFactoryImpl.java | 84 +++++++ .../notes/share/NoteShareFragment.java | 209 +++++++++++------- app/src/main/res/layout/activity_row.xml | 2 +- app/src/main/res/values/strings.xml | 75 +++++++ gradle/verification-metadata.xml | 30 ++- 6 files changed, 323 insertions(+), 86 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java diff --git a/app/build.gradle b/app/build.gradle index c18c1a1cb..513be6a83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,12 +95,17 @@ ext { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' - implementation("com.github.nextcloud:android-library:3d422b28376339c0fbd772e480dbbdc56b7ae1a1") { + implementation 'com.google.guava:guava:31.1-android' + implementation ('commons-httpclient:commons-httpclient:3.1') { + exclude group: 'commons-logging', module: 'commons-logging' + } + + implementation("com.github.nextcloud:android-library:2.19.0") { exclude group: 'org.ogce', module: 'xpp3' } // Nextcloud SSO - implementation 'com.github.nextcloud.android-common:ui:0.24.0' + implementation 'com.github.nextcloud.android-common:ui:0.23.2' implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.2' implementation 'com.github.stefan-niedermann:android-commons:1.0.2' implementation "com.github.stefan-niedermann.nextcloud-commons:sso-glide:$commonsVersion" diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java b/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java new file mode 100644 index 000000000..b9c82d25e --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java @@ -0,0 +1,84 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2019 Chris Narkiewicz + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ + +package it.niedermann.owncloud.notes.share.operations; + +import android.accounts.Account; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.common.PlainClient; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +import com.owncloud.android.lib.common.accounts.AccountUtils; + +import java.io.IOException; + +import it.niedermann.owncloud.notes.shared.user.User; + +public class ClientFactoryImpl implements ClientFactory { + + private Context context; + + public ClientFactoryImpl(Context context) { + this.context = context; + } + + @Override + public OwnCloudClient create(User user) throws CreationException { + try { + return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context); + } catch (OperationCanceledException | + AuthenticatorException | + IOException e) { + throw new CreationException(e); + } + } + + @Override + public NextcloudClient createNextcloudClient(User user) throws CreationException { + try { + return OwnCloudClientFactory.createNextcloudClient(user, context); + } catch (AccountUtils.AccountNotFoundException e) { + throw new CreationException(e); + } + } + + @Override + public OwnCloudClient create(Account account) + throws OperationCanceledException, AuthenticatorException, IOException, + AccountUtils.AccountNotFoundException { + return OwnCloudClientFactory.createOwnCloudClient(account, context); + } + + @Override + public OwnCloudClient create(Account account, Activity currentActivity) + throws OperationCanceledException, AuthenticatorException, IOException, + AccountUtils.AccountNotFoundException { + return OwnCloudClientFactory.createOwnCloudClient(account, context, currentActivity); + } + + @Override + public OwnCloudClient create(Uri uri, boolean followRedirects, boolean useNextcloudUserAgent) { + return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); + } + + @Override + public OwnCloudClient create(Uri uri, boolean followRedirects) { + return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); + } + + @Override + public PlainClient createPlainClient() { + return new PlainClient(context); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java index 5f1004d38..759c8a8f0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java @@ -3,8 +3,10 @@ import android.Manifest; import android.app.Activity; import android.app.SearchManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -15,6 +17,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -22,6 +25,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.widget.SearchView; +import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; @@ -29,6 +33,11 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; +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 java.util.ArrayList; import java.util.List; @@ -39,18 +48,26 @@ import it.niedermann.owncloud.notes.databinding.FragmentNoteShareBinding; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Note; +import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; +import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; +import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; +import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; +import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; +import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; +import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; +import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; import it.niedermann.owncloud.notes.shared.user.User; -import it.niedermann.owncloud.notes.shared.util.ClipboardUtil; import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; -public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, - DisplayUtils.AvatarGenerationListener, - Injectable, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { +public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { private static final String TAG = "NoteShareFragment"; private static final String ARG_NOTE = "NOTE"; private static final String ARG_ACCOUNT = "ACCOUNT"; private static final String ARG_USER = "USER"; + public static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; private FragmentNoteShareBinding binding; private Note note; @@ -58,6 +75,7 @@ public class NoteShareFragment extends Fragment implements ShareeListAdapterList private Account account; private OnEditShareListener onEditShareListener; + private ClientFactoryImpl clientFactory; public static NoteShareFragment newInstance(Note note, User user, Account account) { NoteShareFragment fragment = new NoteShareFragment(); @@ -73,6 +91,8 @@ public static NoteShareFragment newInstance(Note note, User user, Account accoun public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + clientFactory = new ClientFactoryImpl(requireContext()); + if (savedInstanceState != null) { note = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_NOTE, Note.class); account = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_ACCOUNT, Account.class); @@ -107,11 +127,11 @@ public void onActivityCreated(Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentNoteShareBinding.inflate(inflater, container, false); - binding.sharesList.setAdapter(new ShareeListAdapter(fileActivity, + binding.sharesList.setAdapter(new ShareeListAdapter(requireActivity(), new ArrayList<>(), this, user, - note)); + account)); binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); @@ -141,26 +161,29 @@ public void onAttach(@NonNull Context context) { @Override public void onStart() { super.onStart(); - searchConfig.setSearchOnlyUsers(file.isEncrypted()); + UsersAndGroupsSearchConfig.INSTANCE.setSearchOnlyUsers(true); } @Override public void onStop() { super.onStop(); - searchConfig.reset(); + UsersAndGroupsSearchConfig.INSTANCE.reset(); } private void setupView() { setShareWithYou(); - OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + + setupSearchView((SearchManager) requireContext().getSystemService(Context.SEARCH_SERVICE), binding.searchView, requireActivity().getComponentName()); + // viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); - FileDetailSharingFragmentHelper.setupSearchView( - (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), - binding.searchView, - fileActivity.getComponentName()); - viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + /* if (file.canReshare()) { binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); } else { @@ -169,6 +192,39 @@ private void setupView() { binding.pickContactEmailBtn.setVisibility(View.GONE); disableSearchView(binding.searchView); } + */ + + } + + private void setupSearchView(@Nullable SearchManager searchManager, SearchView searchView, + ComponentName componentName) { + if (searchManager == null) { + searchView.setVisibility(View.GONE); + return; + } + + // assumes parent activity is the searchable activity + searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)); + + // do not iconify the widget; expand it by default + searchView.setIconifiedByDefault(false); + + // avoid fullscreen with softkeyboard + searchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + // return true to prevent the query from being processed; + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + // leave it for the parent listener in the hierarchy / default behaviour + return false; + } + }); } private void disableSearchView(View view) { @@ -223,7 +279,7 @@ private void setShareWithYou() { } } - private void copyInternalLink() { + public void copyInternalLink() { if (account == null) { BrandedSnackbar.make(requireView(), getString(R.string.note_share_fragment_could_not_retrieve_url), Snackbar.LENGTH_LONG) .setAnchorView(binding.sharesList) @@ -245,7 +301,7 @@ private void showShareLinkDialog() { String[] packagesToExclude = new String[] { requireContext().getPackageName() }; DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); - chooserDialog.show(getParentFragmentManager(), FileDisplayActivity.FTAG_CHOOSER_DIALOG); + chooserDialog.show(getParentFragmentManager(), FTAG_CHOOSER_DIALOG); } // TODO: Check account.getUrl returning base url? @@ -256,6 +312,7 @@ private String createInternalLink() { // TODO: Capabilities in notes app doesn't have following functions... public void createPublicShareLink() { + /* if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { // password enforced by server, request to the user before trying to create @@ -275,10 +332,12 @@ public void createPublicShareLink() { service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); } + */ + } - private void createSecureFileDrop() { - fileOperationsHelper.shareFolderViaSecureFileDrop(file); + public void createSecureFileDrop() { + // fileOperationsHelper.shareFolderViaSecureFileDrop(file); } /* @@ -303,6 +362,7 @@ public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewTheme } */ private void showSendLinkTo(OCShare publicShare) { + /* if (file.isSharedViaLink()) { if (TextUtils.isEmpty(publicShare.getShareLink())) { fileOperationsHelper.getFileWithLink(file, viewThemeUtils); @@ -311,9 +371,12 @@ private void showSendLinkTo(OCShare publicShare) { showShareLinkDialog(); } } + */ + } public void copyLink(OCShare share) { + /* if (file.isSharedViaLink()) { if (TextUtils.isEmpty(share.getShareLink())) { fileOperationsHelper.getFileWithLink(file, viewThemeUtils); @@ -321,51 +384,22 @@ public void copyLink(OCShare share) { ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); } } + */ + } - /** - * 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).show(); + if (getActivity() != null && !getActivity().isFinishing()) { + new FileDetailSharingMenuBottomSheetDialog(getActivity(), this, share).show(); } } - /** - * show quick sharing permission dialog - * - * @param share - */ @Override public void showPermissionsDialog(OCShare share) { - new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils).show(); + new QuickSharingPermissionsBottomSheetDialog(getActivity(), this, share).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) { if (result.isSuccess()) { refreshUiFromDB(); @@ -384,7 +418,7 @@ private void refreshUiFromDB() { } private void unshareWith(OCShare share) { - fileOperationsHelper.unshareShare(file, share); + // fileOperationsHelper.unshareShare(file, share); } /** @@ -395,7 +429,7 @@ private void unshareWith(OCShare share) { * @param askForPassword if true, password is optional */ public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) { - SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(file, + SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(note, createShare, askForPassword); dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); @@ -411,10 +445,10 @@ public void requestPasswordForShare(OCShare share, boolean askForPassword) { public void showProfileBottomSheet(User user, String shareWith) { if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { new RetrieveHoverCardAsyncTask(user, + account, shareWith, - fileActivity, - clientFactory, - viewThemeUtils).execute(); + getActivity(), + clientFactory).execute(); } } @@ -422,7 +456,7 @@ public void showProfileBottomSheet(User user, String shareWith) { * Get known server capabilities from DB */ public void refreshCapabilitiesFromDB() { - capabilities = fileDataStorageManager.getCapability(user.getAccountName()); + // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); } /** @@ -430,6 +464,7 @@ public void refreshCapabilitiesFromDB() { * before reading database. */ public void refreshSharesFromDB() { + /* OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); if (newFile != null) { file = newFile; @@ -438,7 +473,8 @@ public void refreshSharesFromDB() { ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { - DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); + BrandedSnackbar.make(requireView(), getString(R.string.could_not_retrieve_shares), Snackbar.LENGTH_LONG) + .show(); return; } adapter.getShares().clear(); @@ -458,8 +494,7 @@ public void refreshSharesFromDB() { ShareType.PUBLIC_LINK, ""); - if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares()) && - (!file.isEncrypted() || capabilities.getEndToEndEncryption().isTrue())) { + if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) { final OCShare ocShare = new OCShare(); ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); publicShares.add(ocShare); @@ -468,10 +503,12 @@ public void refreshSharesFromDB() { } adapter.addShares(publicShares); + */ + } private void checkContactPermission() { - if (PermissionUtil.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { pickContactEmail(); } else { requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); @@ -484,7 +521,8 @@ private void pickContactEmail() { if (intent.resolveActivity(requireContext().getPackageManager()) != null) { onContactSelectionResultLauncher.launch(intent); } else { - DisplayUtils.showSnackMessage(requireActivity(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message)); + BrandedSnackbar.make(requireView(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message), Snackbar.LENGTH_LONG) + .show(); } } @@ -492,7 +530,7 @@ private void handleContactResult(@NonNull Uri contactUri) { // Define the projection to get all email addresses. String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; - Cursor cursor = fileActivity.getContentResolver().query(contactUri, projection, null, null, null); + Cursor cursor = requireActivity().getContentResolver().query(contactUri, projection, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { @@ -507,16 +545,19 @@ private void handleContactResult(@NonNull Uri contactUri) { binding.searchView.requestFocus(); }); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address."); } } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); } cursor.close(); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); } } @@ -534,24 +575,22 @@ private boolean containsNoNewPublicShare(List shares) { @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - FileExtensionsKt.logFileSize(file, TAG); - outState.putParcelable(ARG_NOTE, file); + outState.putSerializable(ARG_NOTE, note); + outState.putSerializable(ARG_ACCOUNT, account); outState.putParcelable(ARG_USER, user); } - @Override public void avatarGenerated(Drawable avatarDrawable, Object callContext) { binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); } - @Override public boolean shouldCallGeneratedCallback(String tag, Object callContext) { return false; } private boolean isReshareForbidden(OCShare share) { - return ShareType.FEDERATED == share.getShareType() || - capabilities != null && capabilities.getFilesSharingResharing().isFalse(); + return false; + // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); } @VisibleForTesting @@ -562,13 +601,13 @@ public void search(String query) { @Override public void advancedPermissions(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); + // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); } @Override public void sendNewEmail(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); + // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); } @Override @@ -576,7 +615,8 @@ public void unShare(OCShare share) { unshareWith(share); ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { - DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui)); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); return; } adapter.remove(share); @@ -584,11 +624,14 @@ public void unShare(OCShare share) { @Override public void sendLink(OCShare share) { + /* if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); } else { showSendLinkTo(share); } + */ + } @Override @@ -597,13 +640,12 @@ public void addAnotherLink(OCShare share) { } private void modifyExistingShare(OCShare share, int screenTypePermission) { - onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), - capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); + // onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); } @Override public void onQuickPermissionChanged(OCShare share, int permission) { - fileOperationsHelper.setPermissionsToShare(share, permission); + // fileOperationsHelper.setPermissionsToShare(share, permission); } //launcher for contact permission @@ -612,7 +654,8 @@ public void onQuickPermissionChanged(OCShare share, int permission) { if (isGranted) { pickContactEmail(); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.contact_no_permission); + BrandedSnackbar.make(binding.getRoot(), getString(R.string.contact_no_permission), Snackbar.LENGTH_LONG) + .show(); } }); @@ -623,13 +666,15 @@ public void onQuickPermissionChanged(OCShare share, int permission) { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent == null) { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); return; } Uri contactUri = intent.getData(); if (contactUri == null) { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); return; } diff --git a/app/src/main/res/layout/activity_row.xml b/app/src/main/res/layout/activity_row.xml index ffb8e2e6c..8d6e4d150 100644 --- a/app/src/main/res/layout/activity_row.xml +++ b/app/src/main/res/layout/activity_row.xml @@ -23,7 +23,7 @@ android:layout_height="@dimen/default_icon_size" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/spacer_2x" - android:contentDescription="@string/note_share_activity_activity_icon_content_description" /> + android:contentDescription="@string/note_share_fragment_activity_icon_content_description" /> Choose a category + + @string/link_share_view_only + @string/link_share_allow_upload_and_editing + @string/link_share_file_drop + + + @string/link_share_view_only + @string/link_share_editing + + note activity icon + note share icon + note share external icon note share copy icon note share more icon note share icon @@ -49,6 +61,69 @@ Policy or permissions prevent resharing Could not retrieve URL \"%1$s\" has been shared with you + Contact permission is required. + You must enter a password + Password + Enter an optional password + Skip + Cancel + + Enter a password + OK + Delete + Send email + No app available to handle links + No App available to handle mail address + No actions for this user + + Failed to pick email address. + Failed to update UI + No app available to select contacts + Send link to… + + Send + Internal share link only works for users with access to this folder + Internal share link only works for users with access to this file + Share internal link + Delete Link + Settings + Send new email + Sharing + Share %1$s + Expires %1$s + %1$s + Set expiration date + Share link + Send link + Password-protected + Set password + Share with… + Unset + Add another link + Add new public share link + New name + Share link (%1$s) + Share link + Allow resharing + View only + Editing + Allow upload and editing + File drop (upload only) + Could not retrieve shares + View only + Can edit + File drop + Secure file drop + Share Permissions + + %1$d download remaining + %1$d downloads remaining + + Username + %1$s (group) + %1$s (remote) + %1$s (conversation) + on %1$s Copy link Link copied diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 3b295593a..07f1c940a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -35,7 +35,10 @@ - + + + + @@ -107,6 +110,7 @@ + @@ -130,15 +134,18 @@ + + + @@ -1521,6 +1528,11 @@ + + + + + @@ -5040,6 +5052,14 @@ + + + + + + + + @@ -6389,6 +6409,14 @@ + + + + + + + + From 534257e798fedfc4ba710e20d590044f689705f6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 3 Feb 2025 15:21:40 +0100 Subject: [PATCH 03/25] fix build Signed-off-by: alperozturk --- .../notes/persistence/dao/ShareDao.kt | 10 +- .../notes/share/NoteShareActivity.java | 334 +++++++++++++----- 2 files changed, 255 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt index 15ab98f80..97f27cac0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt @@ -2,15 +2,15 @@ package it.niedermann.owncloud.notes.persistence.dao import androidx.room.Dao import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query import it.niedermann.owncloud.notes.persistence.entity.ShareEntity @Dao interface ShareDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun addShareEntities(entities: List) - @Query("SELECT * FROM share_table WHERE path = :path") - fun getShareEntities(path: String): List + @Insert + fun addShareEntity(entity: ShareEntity) + + @Query("SELECT * FROM share_table WHERE noteRemoteId = :noteRemoteId AND userName = :userName") + fun getShareEntities(noteRemoteId: Long, userName: String): List } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 03856563f..25408bd48 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -14,6 +14,7 @@ import android.text.InputType; import android.text.TextUtils; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; @@ -26,11 +27,17 @@ import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.LinearLayoutManager; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; +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 java.util.ArrayList; import java.util.List; @@ -38,7 +45,9 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.BrandedActivity; import it.niedermann.owncloud.notes.branding.BrandedSnackbar; @@ -46,22 +55,26 @@ import it.niedermann.owncloud.notes.databinding.ActivityNoteShareBinding; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Note; +import it.niedermann.owncloud.notes.persistence.entity.ShareEntity; import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; import it.niedermann.owncloud.notes.share.adapter.SuggestionAdapter; -import it.niedermann.owncloud.notes.share.dialog.NoteShareActivityShareItemActionBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; -import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; +import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; +import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; +import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; import it.niedermann.owncloud.notes.share.repository.ShareRepository; +import it.niedermann.owncloud.notes.shared.user.User; import it.niedermann.owncloud.notes.shared.util.DisplayUtils; import it.niedermann.owncloud.notes.shared.util.ShareUtil; import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; -public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, NoteShareItemAction, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { +public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { private static final String TAG = "NoteShareActivity"; public static final String ARG_NOTE = "NOTE"; @@ -75,6 +88,7 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap private ActivityNoteShareBinding binding; private Note note; private Account account; + private ClientFactoryImpl clientFactory; private ShareRepository repository; public void onCreate(@Nullable Bundle savedInstanceState) { @@ -96,11 +110,12 @@ private void initializeArguments() { throw new IllegalArgumentException("Account cannot be null"); } - executorService.schedule(() -> { + clientFactory = new ClientFactoryImpl(this); + + new Thread(() -> {{ try { final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); repository = new ShareRepository(NoteShareActivity.this, ssoAcc); - repository.getSharesForNotesAndSaveShareEntities(); runOnUiThread(() -> { binding.sharesList.setAdapter(new ShareeListAdapter(this, new ArrayList<>(), this, account)); @@ -115,7 +130,7 @@ private void initializeArguments() { } catch (Exception e) { throw new RuntimeException(e); } - }, 0, TimeUnit.MICROSECONDS); + }}).start(); } @Override @@ -133,6 +148,23 @@ public void onStop() { private void setupView() { setShareWithYou(); setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); + + // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + + // TODO: When to disable? + // binding.pickContactEmailBtn.setVisibility(View.GONE); + // disableSearchView(binding.searchView); + + /* + if (file.canReshare()) { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); + } else { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + } + */ } private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) { @@ -141,7 +173,7 @@ private void setupSearchView(@Nullable SearchManager searchManager, ComponentNam return; } - SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null, account); + SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null); UsersAndGroupsSearchProvider provider = new UsersAndGroupsSearchProvider(this, repository); binding.searchView.setSuggestionsAdapter(suggestionAdapter); @@ -175,11 +207,11 @@ public boolean onQueryTextChange(String newText) { // Schedule a new task with a delay future = executorService.schedule(() -> { - final var isFederationShareAllowed = repository.capabilities().getFederationShare(); - try(var cursor = provider.searchForUsersOrGroups(newText, isFederationShareAllowed)) { - runOnUiThread(() -> { - { + try { + provider.searchForUsersOrGroups(newText, cursor -> { + runOnUiThread(() -> {{ if (cursor == null || cursor.getCount() == 0) { + suggestionAdapter.changeCursor(null); return; } @@ -188,7 +220,7 @@ public boolean onQueryTextChange(String newText) { } binding.progressBar.setVisibility(View.GONE); - } + }}); }); } catch (Exception e) { Log_OC.d(TAG, "Exception setupSearchView.onQueryTextChange: " + e); @@ -234,17 +266,62 @@ private void navigateNoteShareDetail(String shareWith, int shareType) { startActivity(intent); } + private void disableSearchView(View view) { + view.setEnabled(false); + + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + disableSearchView(viewGroup.getChildAt(i)); + } + } + } + + // TODO: Check if note.getAccountId() return note's owner's id private boolean accountOwnsFile() { - String displayName = account.getDisplayName(); - return TextUtils.isEmpty(displayName) || account.getAccountName().split("@")[0].equalsIgnoreCase(displayName); + String noteId = String.valueOf(note.getAccountId()); + return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); } private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); + } else { + + /* + // TODO: How to get owner display name from note? + + binding.sharedWithYouUsername.setText( + String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); + */ + + + loadAvatar(); + + /* + // TODO: Note's note? + String note = file.getNote(); + + if (!TextUtils.isEmpty(note)) { + binding.sharedWithYouNote.setText(file.getNote()); + binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); + } else { + binding.sharedWithYouNoteContainer.setVisibility(View.GONE); + } + */ } } + private void loadAvatar() { + Glide.with(this) + .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) + .placeholder(R.drawable.ic_account_circle_grey_24dp) + .error(R.drawable.ic_account_circle_grey_24dp) + .apply(RequestOptions.circleCropTransform()) + .into(binding.sharedWithYouAvatar); + + binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); + } + public void copyInternalLink() { if (account == null) { DisplayUtils.showSnackMessage(this, getString(R.string.note_share_activity_could_not_retrieve_url)); @@ -254,16 +331,6 @@ public void copyInternalLink() { showShareLinkDialog(); } - @Override - public void createPublicShareLink() { - - } - - @Override - public void createSecureFileDrop() { - - } - private void showShareLinkDialog() { String link = createInternalLink(); @@ -283,15 +350,85 @@ private String createInternalLink() { return baseUri + "/index.php/f/" + note.getRemoteId(); } - @Override - public void copyLink(OCShare share) { + // TODO: Capabilities in notes app doesn't have following functions... + public void createPublicShareLink() { + /* + if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); + + } else { + // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note + // Is CreateShareViaLink operation compatible? + + Intent service = new Intent(fileActivity, OperationsService.class); + service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); + service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); + if (!TextUtils.isEmpty(password)) { + service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); + } + service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); + } + */ + } + + public void createSecureFileDrop() { + // fileOperationsHelper.shareFolderViaSecureFileDrop(file); + } + + /* + // TODO: Cant call getFileWithLink + + public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { + List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + if (shares.size() == SINGLE_LINK_SIZE) { + FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); + } else { + if (fileActivity instanceof FileDisplayActivity) { + ((FileDisplayActivity) fileActivity).showDetails(file, 1); + } else { + showShareFile(file); + } + } + + fileActivity.refreshList(); + } + */ + private void showSendLinkTo(OCShare publicShare) { + /* + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(publicShare.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + // TODO: get link from public share and pass to the function + showShareLinkDialog(); + } + } + */ + } + public void copyLink(OCShare share) { + /* + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(share.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + } + } + */ } @Override public void showSharingMenuActionSheet(OCShare share) { if (!this.isFinishing()) { - new NoteShareActivityShareItemActionBottomSheetDialog(this, this, share).show(); + new FileDetailSharingMenuBottomSheetDialog(this, this, share).show(); } } @@ -300,6 +437,41 @@ public void showPermissionsDialog(OCShare share) { new QuickSharingPermissionsBottomSheetDialog(this, this, share).show(); } + public void onUpdateShareInformation(RemoteOperationResult result) { + if (result.isSuccess()) { + refreshUiFromDB(); + } else { + setupView(); + } + } + + /** + * Get {@link OCShare} instance from DB and updates the UI. + */ + private void refreshUiFromDB() { + refreshSharesFromDB(); + // Updates UI with new state + setupView(); + } + + private void unshareWith(OCShare share) { + repository.removeShare(share.getId()); + } + + /** + * 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(note, + createShare, + askForPassword); + dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + } + @Override public void requestPasswordForShare(OCShare share, boolean askForPassword) { SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(share, askForPassword); @@ -307,15 +479,31 @@ public void requestPasswordForShare(OCShare share, boolean askForPassword) { } @Override - public void showProfileBottomSheet(Account account, String shareWith) { + public void showProfileBottomSheet(User user, String shareWith) { + if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { + new RetrieveHoverCardAsyncTask(user, + account, + shareWith, + this, + clientFactory).execute(); + } } + /** + * Get known server capabilities from DB + */ public void refreshCapabilitiesFromDB() { + // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); } + /** + * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities + * before reading database. + */ public void refreshSharesFromDB() { - executorService.schedule(() -> { + new Thread(() -> { try { + final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { @@ -328,8 +516,8 @@ public void refreshSharesFromDB() { // to show share with users/groups info List shares = new ArrayList<>(); - if (note != null) { - final var shareEntities = repository.getShareEntitiesForSpecificNote(note); + if (note != null && note.getRemoteId() != null) { + final var shareEntities = repository.getShareEntities(note.getRemoteId(), ssoAcc.name); shareEntities.forEach(entity -> { if (entity.getId() != null) { final var share = repository.getShares(entity.getId()); @@ -344,24 +532,23 @@ public void refreshSharesFromDB() { adapter.addShares(shares); // TODO: Will be added later on... - /* - List publicShares = new ArrayList<>(); - - if (containsNoNewPublicShare(adapter.getShares())) { - final OCShare ocShare = new OCShare(); - ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); - publicShares.add(ocShare); - } else { - adapter.removeNewPublicShare(); - } + List publicShares = new ArrayList<>(); + + if (containsNoNewPublicShare(adapter.getShares())) { + final OCShare ocShare = new OCShare(); + ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); + publicShares.add(ocShare); + } else { + adapter.removeNewPublicShare(); + } - adapter.addShares(publicShares); - */ + adapter.addShares(publicShares); }); } catch (Exception e) { - Log_OC.d(TAG, "Exception while refreshSharesFromDB: " + e); + throw new RuntimeException(e); } - }, 0, TimeUnit.MICROSECONDS); + }).start(); + } private void checkContactPermission() { @@ -434,6 +621,7 @@ public void onSaveInstanceState(@NonNull Bundle outState) { private boolean isReshareForbidden(OCShare share) { return false; + // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); } @VisibleForTesting @@ -455,26 +643,24 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { - executorService.schedule(() -> { - final var result = repository.removeShare(share.getId()); - - runOnUiThread(() -> { - if (result) { - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - if (adapter == null) { - DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.email_pick_failed)); - return; - } - adapter.remove(share); - } else { - DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.failed_the_remove_share)); - } - }); - }, 0, TimeUnit.MICROSECONDS); + unshareWith(share); + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + if (adapter == null) { + DisplayUtils.showSnackMessage(this, getString(R.string.email_pick_failed)); + return; + } + adapter.remove(share); } @Override public void sendLink(OCShare share) { + /* + if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { + FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); + } else { + showSendLinkTo(share); + } + */ } @Override @@ -488,34 +674,19 @@ private void modifyExistingShare(OCShare share, int screenTypePermission) { bundle.putSerializable(NoteShareDetailActivity.ARG_OCSHARE, share); bundle.putInt(NoteShareDetailActivity.ARG_SCREEN_TYPE, screenTypePermission); bundle.putBoolean(NoteShareDetailActivity.ARG_RESHARE_SHOWN, !isReshareForbidden(share)); - bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, getExpDateShown()); + bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, true); // TODO: capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18) Intent intent = new Intent(this, NoteShareDetailActivity.class); intent.putExtras(bundle); startActivity(intent); } - private boolean getExpDateShown() { - try { - final var capabilities = repository.capabilities(); - final var majorVersionAsString = capabilities.getNextcloudMajorVersion(); - if (majorVersionAsString != null) { - final var majorVersion = Integer.parseInt(majorVersionAsString); - return majorVersion >= 18; - } - - return false; - } catch (NumberFormatException e) { - Log_OC.d(TAG, "Exception while getting expDateShown"); - return false; - } - } - @Override public void onQuickPermissionChanged(OCShare share, int permission) { repository.updateSharePermission(share.getId(), permission); } + //launcher for contact permission private final ActivityResultLauncher requestContactPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { @@ -526,6 +697,7 @@ public void onQuickPermissionChanged(OCShare share, int permission) { } }); + //launcher to handle contact selection private final ActivityResultLauncher onContactSelectionResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { @@ -556,10 +728,4 @@ public void applyBrand(int color) { util.androidx.themeToolbarSearchView(binding.searchView); util.platform.themeHorizontalProgressBar(binding.progressBar); } - - @Override - protected void onDestroy() { - executorService.shutdown(); - super.onDestroy(); - } } From 54ff709edb46ee9d330a6b426de2d9f0031b6e00 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:07:33 +0100 Subject: [PATCH 04/25] add createPublicShareLink Signed-off-by: alperozturk --- .../sync/CapabilitiesDeserializer.java | 22 ++ .../notes/share/NoteShareActivity.java | 364 ++++++------------ .../share/adapter/ShareeListAdapter.java | 4 +- .../adapter/holder/LinkShareViewHolder.java | 6 +- .../notes/share/model/CreateShareRequest.kt | 6 +- .../notes/share/repository/ShareRepository.kt | 2 +- .../notes/shared/model/Capabilities.java | 21 + 7 files changed, 172 insertions(+), 253 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java index 48b5428f5..7cc6679d2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java @@ -43,6 +43,20 @@ public class CapabilitiesDeserializer implements JsonDeserializer private static final String CAPABILITIES_FILES_SHARING = "files_sharing"; private static final String VERSION = "version"; + /* + if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); + + } else { + // create without password if not enforced by server or we don't know if enforced; + fileOperationsHelper.shareFileViaPublicShare(file, null); + } + + password -> {JsonObject@32644} "{"enforced":false,"askForOptionalPassword":false}" + */ @Override public Capabilities deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { final var response = new Capabilities(); @@ -69,6 +83,14 @@ public Capabilities deserialize(JsonElement json, Type typeOfT, JsonDeserializat final var outgoing = federation.get("outgoing"); response.setFederationShare(outgoing.getAsBoolean()); + + final var publicObject = filesSharing.getAsJsonObject("public"); + final var password = publicObject.getAsJsonObject("password"); + final var enforced = password.getAsJsonPrimitive("enforced"); + final var askForOptionalPassword = password.getAsJsonPrimitive("askForOptionalPassword"); + + response.setPublicPasswordEnforced(enforced.getAsBoolean()); + response.setAskForOptionalPassword(askForOptionalPassword.getAsBoolean()); } if (capabilities.has(CAPABILITIES_NOTES)) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 25408bd48..4295b1896 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -14,7 +14,6 @@ import android.text.InputType; import android.text.TextUtils; import android.view.View; -import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; @@ -27,17 +26,12 @@ import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.LinearLayoutManager; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; -import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; -import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; 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 java.util.ArrayList; import java.util.List; @@ -45,9 +39,7 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.BrandedActivity; import it.niedermann.owncloud.notes.branding.BrandedSnackbar; @@ -55,26 +47,25 @@ import it.niedermann.owncloud.notes.databinding.ActivityNoteShareBinding; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Note; -import it.niedermann.owncloud.notes.persistence.entity.ShareEntity; import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; import it.niedermann.owncloud.notes.share.adapter.SuggestionAdapter; -import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.NoteShareActivityShareItemActionBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; -import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; +import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; +import it.niedermann.owncloud.notes.share.model.CreateShareRequest; import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; -import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; -import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; import it.niedermann.owncloud.notes.share.repository.ShareRepository; -import it.niedermann.owncloud.notes.shared.user.User; +import it.niedermann.owncloud.notes.shared.model.Capabilities; import it.niedermann.owncloud.notes.shared.util.DisplayUtils; import it.niedermann.owncloud.notes.shared.util.ShareUtil; +import it.niedermann.owncloud.notes.shared.util.clipboard.ClipboardUtil; import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; -public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { +public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, NoteShareItemAction, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { private static final String TAG = "NoteShareActivity"; public static final String ARG_NOTE = "NOTE"; @@ -88,8 +79,8 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap private ActivityNoteShareBinding binding; private Note note; private Account account; - private ClientFactoryImpl clientFactory; private ShareRepository repository; + private Capabilities capabilities; public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -110,12 +101,12 @@ private void initializeArguments() { throw new IllegalArgumentException("Account cannot be null"); } - clientFactory = new ClientFactoryImpl(this); - - new Thread(() -> {{ + executorService.schedule(() -> { try { final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); repository = new ShareRepository(NoteShareActivity.this, ssoAcc); + capabilities = repository.capabilities(); + repository.getSharesForNotesAndSaveShareEntities(); runOnUiThread(() -> { binding.sharesList.setAdapter(new ShareeListAdapter(this, new ArrayList<>(), this, account)); @@ -130,7 +121,7 @@ private void initializeArguments() { } catch (Exception e) { throw new RuntimeException(e); } - }}).start(); + }, 0, TimeUnit.MICROSECONDS); } @Override @@ -148,23 +139,6 @@ public void onStop() { private void setupView() { setShareWithYou(); setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); - - // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); - - // TODO: When to disable? - // binding.pickContactEmailBtn.setVisibility(View.GONE); - // disableSearchView(binding.searchView); - - /* - if (file.canReshare()) { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); - } else { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - } - */ } private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) { @@ -173,7 +147,7 @@ private void setupSearchView(@Nullable SearchManager searchManager, ComponentNam return; } - SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null); + SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null, account); UsersAndGroupsSearchProvider provider = new UsersAndGroupsSearchProvider(this, repository); binding.searchView.setSuggestionsAdapter(suggestionAdapter); @@ -207,11 +181,16 @@ public boolean onQueryTextChange(String newText) { // Schedule a new task with a delay future = executorService.schedule(() -> { - try { - provider.searchForUsersOrGroups(newText, cursor -> { - runOnUiThread(() -> {{ + if (capabilities == null) { + Log_OC.d(TAG, "Capabilities cannot be null"); + return; + } + + final var isFederationShareAllowed = capabilities.getFederationShare(); + try(var cursor = provider.searchForUsersOrGroups(newText, isFederationShareAllowed)) { + runOnUiThread(() -> { + { if (cursor == null || cursor.getCount() == 0) { - suggestionAdapter.changeCursor(null); return; } @@ -220,7 +199,7 @@ public boolean onQueryTextChange(String newText) { } binding.progressBar.setVisibility(View.GONE); - }}); + } }); } catch (Exception e) { Log_OC.d(TAG, "Exception setupSearchView.onQueryTextChange: " + e); @@ -266,62 +245,17 @@ private void navigateNoteShareDetail(String shareWith, int shareType) { startActivity(intent); } - private void disableSearchView(View view) { - view.setEnabled(false); - - if (view instanceof ViewGroup viewGroup) { - for (int i = 0; i < viewGroup.getChildCount(); i++) { - disableSearchView(viewGroup.getChildAt(i)); - } - } - } - - // TODO: Check if note.getAccountId() return note's owner's id private boolean accountOwnsFile() { - String noteId = String.valueOf(note.getAccountId()); - return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); + String displayName = account.getDisplayName(); + return TextUtils.isEmpty(displayName) || account.getAccountName().split("@")[0].equalsIgnoreCase(displayName); } private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); - } else { - - /* - // TODO: How to get owner display name from note? - - binding.sharedWithYouUsername.setText( - String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); - */ - - - loadAvatar(); - - /* - // TODO: Note's note? - String note = file.getNote(); - - if (!TextUtils.isEmpty(note)) { - binding.sharedWithYouNote.setText(file.getNote()); - binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); - } else { - binding.sharedWithYouNoteContainer.setVisibility(View.GONE); - } - */ } } - private void loadAvatar() { - Glide.with(this) - .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) - .placeholder(R.drawable.ic_account_circle_grey_24dp) - .error(R.drawable.ic_account_circle_grey_24dp) - .apply(RequestOptions.circleCropTransform()) - .into(binding.sharedWithYouAvatar); - - binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); - } - public void copyInternalLink() { if (account == null) { DisplayUtils.showSnackMessage(this, getString(R.string.note_share_activity_could_not_retrieve_url)); @@ -331,6 +265,33 @@ public void copyInternalLink() { showShareLinkDialog(); } + @Override + public void createPublicShareLink() { + if (capabilities == null) { + Log_OC.d(TAG, "Capabilities cannot be null"); + return; + } + + if (capabilities.getPublicPasswordEnforced() || capabilities.getAskForOptionalPassword()) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, capabilities.getAskForOptionalPassword()); + } else { + executorService.schedule(() -> { + repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); + }, 0, TimeUnit.MICROSECONDS); + } + } + + public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) { + SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(note, createShare, askForPassword); + dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + } + + @Override + public void createSecureFileDrop() { + + } + private void showShareLinkDialog() { String link = createInternalLink(); @@ -350,85 +311,25 @@ private String createInternalLink() { return baseUri + "/index.php/f/" + note.getRemoteId(); } - // TODO: Capabilities in notes app doesn't have following functions... - public void createPublicShareLink() { - /* - if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - - } else { - // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note - // Is CreateShareViaLink operation compatible? - - Intent service = new Intent(fileActivity, OperationsService.class); - service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); - service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); - if (!TextUtils.isEmpty(password)) { - service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); - } - service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); - mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); - } - */ - } - - public void createSecureFileDrop() { - // fileOperationsHelper.shareFolderViaSecureFileDrop(file); - } - - /* - // TODO: Cant call getFileWithLink - - public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { - List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - if (shares.size() == SINGLE_LINK_SIZE) { - FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); - } else { - if (fileActivity instanceof FileDisplayActivity) { - ((FileDisplayActivity) fileActivity).showDetails(file, 1); - } else { - showShareFile(file); - } - } - - fileActivity.refreshList(); - } - */ - private void showSendLinkTo(OCShare publicShare) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(publicShare.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - // TODO: get link from public share and pass to the function - showShareLinkDialog(); - } - } - */ - } - + @Override public void copyLink(OCShare share) { /* if (file.isSharedViaLink()) { if (TextUtils.isEmpty(share.getShareLink())) { fileOperationsHelper.getFileWithLink(file, viewThemeUtils); } else { - ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + ClipboardUtil.copyToClipboard(this, share.getShareLink()); } } */ + + ClipboardUtil.copyToClipboard(this, share.getShareLink()); } @Override public void showSharingMenuActionSheet(OCShare share) { if (!this.isFinishing()) { - new FileDetailSharingMenuBottomSheetDialog(this, this, share).show(); + new NoteShareActivityShareItemActionBottomSheetDialog(this, this, share).show(); } } @@ -437,41 +338,6 @@ public void showPermissionsDialog(OCShare share) { new QuickSharingPermissionsBottomSheetDialog(this, this, share).show(); } - public void onUpdateShareInformation(RemoteOperationResult result) { - if (result.isSuccess()) { - refreshUiFromDB(); - } else { - setupView(); - } - } - - /** - * Get {@link OCShare} instance from DB and updates the UI. - */ - private void refreshUiFromDB() { - refreshSharesFromDB(); - // Updates UI with new state - setupView(); - } - - private void unshareWith(OCShare share) { - repository.removeShare(share.getId()); - } - - /** - * 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(note, - createShare, - askForPassword); - dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); - } - @Override public void requestPasswordForShare(OCShare share, boolean askForPassword) { SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(share, askForPassword); @@ -479,31 +345,15 @@ public void requestPasswordForShare(OCShare share, boolean askForPassword) { } @Override - public void showProfileBottomSheet(User user, String shareWith) { - if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - new RetrieveHoverCardAsyncTask(user, - account, - shareWith, - this, - clientFactory).execute(); - } + public void showProfileBottomSheet(Account account, String shareWith) { } - /** - * Get known server capabilities from DB - */ public void refreshCapabilitiesFromDB() { - // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); } - /** - * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities - * before reading database. - */ public void refreshSharesFromDB() { - new Thread(() -> { + executorService.schedule(() -> { try { - final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { @@ -516,8 +366,8 @@ public void refreshSharesFromDB() { // to show share with users/groups info List shares = new ArrayList<>(); - if (note != null && note.getRemoteId() != null) { - final var shareEntities = repository.getShareEntities(note.getRemoteId(), ssoAcc.name); + if (note != null) { + final var shareEntities = repository.getShareEntitiesForSpecificNote(note); shareEntities.forEach(entity -> { if (entity.getId() != null) { final var share = repository.getShares(entity.getId()); @@ -530,25 +380,26 @@ public void refreshSharesFromDB() { runOnUiThread(() -> { adapter.addShares(shares); - - // TODO: Will be added later on... - List publicShares = new ArrayList<>(); - - if (containsNoNewPublicShare(adapter.getShares())) { - final OCShare ocShare = new OCShare(); - ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); - publicShares.add(ocShare); - } else { - adapter.removeNewPublicShare(); - } - - adapter.addShares(publicShares); + addPublicShares(adapter); }); } catch (Exception e) { - throw new RuntimeException(e); + Log_OC.d(TAG, "Exception while refreshSharesFromDB: " + e); } - }).start(); + }, 0, TimeUnit.MICROSECONDS); + } + + private void addPublicShares(ShareeListAdapter adapter) { + List publicShares = new ArrayList<>(); + + if (containsNoNewPublicShare(adapter.getShares())) { + final OCShare ocShare = new OCShare(); + ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); + publicShares.add(ocShare); + } else { + adapter.removeNewPublicShare(); + } + adapter.addShares(publicShares); } private void checkContactPermission() { @@ -621,7 +472,6 @@ public void onSaveInstanceState(@NonNull Bundle outState) { private boolean isReshareForbidden(OCShare share) { return false; - // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); } @VisibleForTesting @@ -643,24 +493,26 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { - unshareWith(share); - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - if (adapter == null) { - DisplayUtils.showSnackMessage(this, getString(R.string.email_pick_failed)); - return; - } - adapter.remove(share); + executorService.schedule(() -> { + final var result = repository.removeShare(share.getId()); + + runOnUiThread(() -> { + if (result) { + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + if (adapter == null) { + DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.email_pick_failed)); + return; + } + adapter.remove(share); + } else { + DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.failed_the_remove_share)); + } + }); + }, 0, TimeUnit.MICROSECONDS); } @Override public void sendLink(OCShare share) { - /* - if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { - FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); - } else { - showSendLinkTo(share); - } - */ } @Override @@ -674,19 +526,38 @@ private void modifyExistingShare(OCShare share, int screenTypePermission) { bundle.putSerializable(NoteShareDetailActivity.ARG_OCSHARE, share); bundle.putInt(NoteShareDetailActivity.ARG_SCREEN_TYPE, screenTypePermission); bundle.putBoolean(NoteShareDetailActivity.ARG_RESHARE_SHOWN, !isReshareForbidden(share)); - bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, true); // TODO: capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18) + bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, getExpDateShown()); Intent intent = new Intent(this, NoteShareDetailActivity.class); intent.putExtras(bundle); startActivity(intent); } + private boolean getExpDateShown() { + try { + if (capabilities == null) { + Log_OC.d(TAG, "Capabilities cannot be null"); + return false; + } + + final var majorVersionAsString = capabilities.getNextcloudMajorVersion(); + if (majorVersionAsString != null) { + final var majorVersion = Integer.parseInt(majorVersionAsString); + return majorVersion >= 18; + } + + return false; + } catch (NumberFormatException e) { + Log_OC.d(TAG, "Exception while getting expDateShown"); + return false; + } + } + @Override public void onQuickPermissionChanged(OCShare share, int permission) { repository.updateSharePermission(share.getId(), permission); } - //launcher for contact permission private final ActivityResultLauncher requestContactPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { @@ -697,7 +568,6 @@ public void onQuickPermissionChanged(OCShare share, int permission) { } }); - //launcher to handle contact selection private final ActivityResultLauncher onContactSelectionResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { @@ -728,4 +598,10 @@ public void applyBrand(int color) { util.androidx.themeToolbarSearchView(binding.searchView); util.platform.themeHorizontalProgressBar(binding.progressBar); } + + @Override + protected void onDestroy() { + executorService.shutdown(); + super.onDestroy(); + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/ShareeListAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/ShareeListAdapter.java index c9550cfe4..157a61be5 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/ShareeListAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/ShareeListAdapter.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.List; +import it.niedermann.owncloud.notes.databinding.ItemAddPublicShareBinding; import it.niedermann.owncloud.notes.databinding.ItemInternalShareLinkBinding; import it.niedermann.owncloud.notes.databinding.ItemShareLinkShareBinding; import it.niedermann.owncloud.notes.databinding.ItemShareShareBinding; @@ -62,7 +63,6 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int false), activity); } - /* case NEW_PUBLIC_LINK -> { return new NewLinkShareViewHolder( ItemAddPublicShareBinding.inflate(LayoutInflater.from(activity), @@ -70,8 +70,6 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int false) ); } - */ - case INTERNAL -> { return new InternalShareViewHolder( ItemInternalShareLinkBinding.inflate(LayoutInflater.from(activity), parent, false), diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/holder/LinkShareViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/holder/LinkShareViewHolder.java index daecc2cc1..f3a5e1c6e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/holder/LinkShareViewHolder.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/adapter/holder/LinkShareViewHolder.java @@ -84,7 +84,9 @@ private void setPermissionName(OCShare publicShare, String permissionName) { @Override public void applyBrand(int color) { final var util = BrandingUtil.of(color, context); - util.androidx.colorPrimaryTextViewElement(binding.permissionName); - util.platform.colorImageViewBackgroundAndIcon(binding.icon); + if (binding != null) { + util.androidx.colorPrimaryTextViewElement(binding.permissionName); + util.platform.colorImageViewBackgroundAndIcon(binding.icon); + } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareRequest.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareRequest.kt index 52a6fff5a..8ae313b81 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareRequest.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareRequest.kt @@ -17,11 +17,11 @@ data class CreateShareRequest( val publicUpload: String, @Expose - val password: String, + val password: String?, @Expose - val permissions: Int, + val permissions: Int?, @Expose - val note: String + val note: String? ) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt index 2a85fcb97..69f890803 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt @@ -38,7 +38,7 @@ class ShareRepository(private val applicationContext: Context, private val accou return notesPathResponse.body() } - private fun getNotePath(note: Note): String? { + fun getNotePath(note: Note): String? { val notesPathResponseResult = getNotesPathResponseResult() ?: return null val notesPath = notesPathResponseResult.notesPath val notesSuffix = notesPathResponseResult.fileSuffix diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java index 82bf7299b..efb9e4918 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java @@ -39,6 +39,25 @@ public class Capabilities implements Serializable { private boolean directEditingAvailable; + private boolean publicPasswordEnforced; + private boolean askForOptionalPassword; + + public boolean getPublicPasswordEnforced() { + return publicPasswordEnforced; + } + + public void setPublicPasswordEnforced(boolean value) { + this.publicPasswordEnforced = value; + } + + public boolean getAskForOptionalPassword() { + return askForOptionalPassword; + } + + public void setAskForOptionalPassword(boolean value) { + this.askForOptionalPassword = value; + } + public void setApiVersion(String apiVersion) { this.apiVersion = apiVersion; } @@ -124,6 +143,8 @@ public String toString() { ", nextcloudMinorVersion='" + nextcloudMinorVersion + '\'' + ", nextcloudMicroVersion='" + nextcloudMicroVersion + '\'' + ", federationShare=" + federationShare + + ", publicPasswordEnforced=" + publicPasswordEnforced + + ", askForOptionalPassword=" + askForOptionalPassword + ", color=" + color + ", textColor=" + textColor + ", eTag='" + eTag + '\'' + From 74b2d47015646d8d5cc240a9e9c08ea1934e3ba3 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:13:40 +0100 Subject: [PATCH 05/25] fix createPublicShareLink Signed-off-by: alperozturk --- .../niedermann/owncloud/notes/share/NoteShareActivity.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 4295b1896..8eb26fd20 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -277,7 +277,10 @@ public void createPublicShareLink() { requestPasswordForShareViaLink(true, capabilities.getAskForOptionalPassword()); } else { executorService.schedule(() -> { - repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); + final var result = repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); + if (result != null) { + runOnUiThread(this::recreate); + } }, 0, TimeUnit.MICROSECONDS); } } From 95be6c1fbaeca242b86091d9bd4c3ec80ab0f2b4 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:18:39 +0100 Subject: [PATCH 06/25] add copyAndShareFileLink Signed-off-by: alperozturk --- .../owncloud/notes/share/NoteShareActivity.java | 14 +++++++++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 8eb26fd20..06de6b968 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -326,7 +326,19 @@ public void copyLink(OCShare share) { } */ - ClipboardUtil.copyToClipboard(this, share.getShareLink()); + if (TextUtils.isEmpty(share.getShareLink())) { + copyAndShareFileLink(share.getShareLink()); + } else { + ClipboardUtil.copyToClipboard(this, share.getShareLink()); + } + } + + private void copyAndShareFileLink(String link) { + ClipboardUtil.copyToClipboard(this, link, false); + Snackbar snackbar = Snackbar + .make(this.findViewById(android.R.id.content), R.string.clipboard_text_copied, Snackbar.LENGTH_LONG) + .setAction(R.string.share, v -> showShareLinkDialog()); + snackbar.show(); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 944bfa5c3..1d238f50a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -126,6 +126,7 @@ on %1$s Copy link + Share Link copied Received no text to copy to clipboard Unexpected error while copying to clipboard From a2b285bd36f95df7dd0d0a3e6ecd8d681ff58db6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:20:48 +0100 Subject: [PATCH 07/25] clean imports Signed-off-by: alperozturk --- .../it/niedermann/owncloud/notes/share/NoteShareActivity.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 06de6b968..ea1b29236 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -29,7 +29,6 @@ import com.google.android.material.snackbar.Snackbar; import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.lib.resources.shares.ShareType; @@ -56,7 +55,6 @@ import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; -import it.niedermann.owncloud.notes.share.model.CreateShareRequest; import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; import it.niedermann.owncloud.notes.share.repository.ShareRepository; import it.niedermann.owncloud.notes.shared.model.Capabilities; From 876f99709fa192d15b6935fddb2c260c921f2988 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:26:51 +0100 Subject: [PATCH 08/25] add fix me Signed-off-by: alperozturk --- .../owncloud/notes/share/NoteShareActivity.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index ea1b29236..3f5183085 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -260,7 +260,8 @@ public void copyInternalLink() { return; } - showShareLinkDialog(); + final var link = createInternalLink(); + showShareLinkDialog(link); } @Override @@ -293,9 +294,7 @@ public void createSecureFileDrop() { } - private void showShareLinkDialog() { - String link = createInternalLink(); - + private void showShareLinkDialog(String link) { Intent intentToShareLink = new Intent(Intent.ACTION_SEND); intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); @@ -324,6 +323,7 @@ public void copyLink(OCShare share) { } */ + // FIXME: getShareLink is empty if (TextUtils.isEmpty(share.getShareLink())) { copyAndShareFileLink(share.getShareLink()); } else { @@ -335,7 +335,7 @@ private void copyAndShareFileLink(String link) { ClipboardUtil.copyToClipboard(this, link, false); Snackbar snackbar = Snackbar .make(this.findViewById(android.R.id.content), R.string.clipboard_text_copied, Snackbar.LENGTH_LONG) - .setAction(R.string.share, v -> showShareLinkDialog()); + .setAction(R.string.share, v -> showShareLinkDialog(link)); snackbar.show(); } From ae004901f8c0df4b9cf774abd0fd90d78ba9282d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:48:45 +0100 Subject: [PATCH 09/25] add missing field for oc share Signed-off-by: alperozturk --- .../owncloud/notes/persistence/entity/ShareEntity.kt | 1 + .../owncloud/notes/share/NoteShareActivity.java | 1 - .../notes/share/model/CreateShareResponse.kt | 12 ++++++++++++ .../share/model/CreateShareResponseExtensions.kt | 6 +++--- .../notes/share/repository/ShareRepository.kt | 4 +++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/ShareEntity.kt b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/ShareEntity.kt index 4d0f226f4..943a5e6aa 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/ShareEntity.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/ShareEntity.kt @@ -18,4 +18,5 @@ data class ShareEntity( val displayname_file_owner: String? = null, val uid_owner: String? = null, val displayname_owner: String? = null, + val url: String? = null ) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 3f5183085..47fb3b56b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -323,7 +323,6 @@ public void copyLink(OCShare share) { } */ - // FIXME: getShareLink is empty if (TextUtils.isEmpty(share.getShareLink())) { copyAndShareFileLink(share.getShareLink()); } else { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponse.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponse.kt index 1d39649e3..28aef0a38 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponse.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponse.kt @@ -122,4 +122,16 @@ data class CreateShareResponse( @Expose val attributes: Any?, + + @Expose + @SerializedName("url") + val url: String?, + + @Expose + @SerializedName("token") + val token: String?, + + @Expose + @SerializedName("password") + val password: String? ) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponseExtensions.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponseExtensions.kt index c3646c4ff..e6eb6bc6a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponseExtensions.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/model/CreateShareResponseExtensions.kt @@ -19,12 +19,12 @@ fun List.toOCShare(): List { path = response.path permissions = response.permissions.toInt() sharedDate = response.stime - token = null + token = response.token sharedWithDisplayName = response.shareWithDisplayname isFolder = response.itemType == "folder" userId = response.uidOwner - shareLink = null - isPasswordProtected = false + shareLink = response.url + isPasswordProtected = !response.password.isNullOrEmpty() note = response.note isHideFileDownload = response.hideDownload > 0 label = response.label diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt index 69f890803..b147b1e5c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt @@ -75,6 +75,7 @@ class ShareRepository(private val applicationContext: Context, private val accou val displayNameFileOwner = map?.get("displayname_file_owner") as? String val uidOwner = map?.get("uid_owner") as? String val displayNameOwner = map?.get("displayname_owner") as? String + val url = map?.get("url") as? String id?.toInt()?.let { val entity = ShareEntity( @@ -87,7 +88,8 @@ class ShareRepository(private val applicationContext: Context, private val accou uid_file_owner = uidFileOwner, displayname_file_owner = displayNameFileOwner, uid_owner = uidOwner, - displayname_owner = displayNameOwner + displayname_owner = displayNameOwner, + url = url ) entities.add(entity) From 96d05c3895a703e8752f71d67134b1b7ffdff677 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 6 Feb 2025 10:04:06 +0100 Subject: [PATCH 10/25] add isSharedViaLink Signed-off-by: alperozturk --- .../owncloud/notes/edit/BaseNoteFragment.java | 2 +- .../owncloud/notes/edit/EditNoteActivity.java | 2 +- .../notes/persistence/NotesRepository.java | 12 ++++++---- .../notes/persistence/dao/NoteDao.java | 18 +++++++------- .../notes/persistence/entity/Note.java | 24 ++++++++++++++++--- .../notes/share/NoteShareActivity.java | 14 ++++------- .../notes/share/repository/ShareRepository.kt | 14 +++++++---- 7 files changed, 55 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java index b7011fbb9..fbbf86c2a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java @@ -122,7 +122,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (content == null) { throw new IllegalArgumentException(PARAM_NOTE_ID + " is not given, argument " + PARAM_NEWNOTE + " is missing and " + PARAM_CONTENT + " is missing."); } else { - note = new Note(-1, null, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, getString(R.string.category_readonly), false, null, DBStatus.VOID, -1, "", 0); + note = new Note(-1, null, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, getString(R.string.category_readonly), false, null, DBStatus.VOID, -1, "", 0, false); requireActivity().runOnUiThread(() -> onNoteLoaded(note)); requireActivity().invalidateOptionsMenu(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java index cb59994eb..345b278d7 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java @@ -302,7 +302,7 @@ private void launchNewNote() { if (content == null) { content = ""; } - final var newNote = new Note(null, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, this), content, categoryTitle, favorite, null); + final var newNote = new Note(null, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, this), content, categoryTitle, favorite, null, false); fragment = getNewNoteFragment(newNote); replaceFragment(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java index 11a0ffa00..6f54244d2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java @@ -480,7 +480,7 @@ public NotesListWidgetData getNoteListWidgetData(int appWidgetId) { @NonNull @MainThread public LiveData addNoteAndSync(Account account, Note note) { - final var entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0); + final var entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0, note.isSharedViaLink()); final var ret = new MutableLiveData(); executor.submit(() -> ret.postValue(addNote(account.getId(), entity))); return map(ret, newNote -> { @@ -508,7 +508,7 @@ public Note addNote(long accountId, @NonNull Note note) { @MainThread public LiveData moveNoteToAnotherAccount(Account account, @NonNull Note note) { - final var fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null); + final var fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null, note.isSharedViaLink()); fullNote.setStatus(DBStatus.LOCAL_EDITED); deleteNoteAndSync(account, note.getId()); return addNoteAndSync(account, fullNote); @@ -570,7 +570,7 @@ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNo // https://github.com/nextcloud/notes-android/issues/1198 @Nullable final Long remoteId = db.getNoteDao().getRemoteId(oldNote.getId()); if (newContent == null) { - newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY()); + newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY(), oldNote.isSharedViaLink()); } else { final String title; if (newTitle != null) { @@ -584,7 +584,7 @@ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNo title = oldNote.getTitle(); } } - newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY()); + newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY(), oldNote.isSharedViaLink()); } int rows = db.getNoteDao().updateNote(newNote); // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly. @@ -974,4 +974,8 @@ public void addShareEntities(List entities) { public List getShareEntities(String path) { return db.getShareDao().getShareEntities(path); } + + public void updateNote(Note note) { + db.getNoteDao().updateNote(note); + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java index 07b7e4f4a..7b475c5c7 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java @@ -38,14 +38,14 @@ public interface NoteDao { String getNoteById = "SELECT * FROM NOTE WHERE id = :id"; String count = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId"; String countFavorites = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId AND favorite = 1"; - String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC"; - String searchRecentLexicographically = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; - String searchFavoritesByModified = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY modified DESC"; - String searchFavoritesLexicographically = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY title COLLATE LOCALIZED ASC"; - String searchUncategorizedByModified = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, modified DESC"; - String searchUncategorizedLexicographically = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; - String searchCategoryByModified = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, modified DESC"; - String searchCategoryLexicographically = "SELECT id, remoteId, accountId, title, favorite, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, title COLLATE LOCALIZED ASC"; + String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC"; + String searchRecentLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; + String searchFavoritesByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY modified DESC"; + String searchFavoritesLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY title COLLATE LOCALIZED ASC"; + String searchUncategorizedByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, modified DESC"; + String searchUncategorizedLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; + String searchCategoryByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, modified DESC"; + String searchCategoryLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, title COLLATE LOCALIZED ASC"; @Query(getNoteById) LiveData getNoteById$(long id); @@ -141,7 +141,7 @@ public interface NoteDao { * Gets a list of {@link Note} objects with filled {@link Note#id} and {@link Note#remoteId}, * where {@link Note#remoteId} is not null */ - @Query("SELECT id, remoteId, 0 as accountId, '' as title, 0 as favorite, '' as excerpt, 0 as modified, '' as eTag, 0 as status, '' as category, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND remoteId IS NOT NULL") + @Query("SELECT id, remoteId, 0 as accountId, '' as title, 0 as favorite, 0 as share_by_link, '' as excerpt, 0 as modified, '' as eTag, 0 as status, '' as category, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND remoteId IS NOT NULL") List getRemoteIdAndId(long accountId); /** diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java index 886eb8786..7b8285670 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java @@ -38,6 +38,7 @@ @Index(name = "IDX_NOTE_ACCOUNTID", value = "accountId"), @Index(name = "IDX_NOTE_CATEGORY", value = "category"), @Index(name = "IDX_NOTE_FAVORITE", value = "favorite"), + @Index(name = "IDX_NOTE_IS_SHARED_VIA_LINK", value = "share_by_link"), @Index(name = "IDX_NOTE_MODIFIED", value = "modified"), @Index(name = "IDX_NOTE_REMOTEID", value = "remoteId"), @Index(name = "IDX_NOTE_STATUS", value = "status") @@ -81,6 +82,11 @@ public class Note implements Serializable, Item { @ColumnInfo(defaultValue = "0") private boolean favorite = false; + @Expose + @ColumnInfo(defaultValue = "0", name = "share_by_link") + @SerializedName("share_by_link") + private boolean isSharedViaLink = false; + @Expose @Nullable @SerializedName("etag") @@ -98,7 +104,7 @@ public Note() { } @Ignore - public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String eTag) { + public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String eTag, boolean isSharedViaLink) { this.remoteId = remoteId; this.title = title; this.modified = modified; @@ -106,11 +112,12 @@ public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull Strin this.favorite = favorite; this.category = category; this.eTag = eTag; + this.isSharedViaLink = isSharedViaLink; } @Ignore - public Note(long id, @Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String etag, @NonNull DBStatus status, long accountId, @NonNull String excerpt, int scrollY) { - this(remoteId, modified, title, content, category, favorite, etag); + public Note(long id, @Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String etag, @NonNull DBStatus status, long accountId, @NonNull String excerpt, int scrollY, boolean isSharedViaLink) { + this(remoteId, modified, title, content, category, favorite, etag, isSharedViaLink); this.id = id; this.status = status; this.accountId = accountId; @@ -126,6 +133,14 @@ public void setId(long id) { this.id = id; } + public boolean isSharedViaLink() { + return isSharedViaLink; + } + + public void setIsSharedViaLink(boolean value) { + this.isSharedViaLink = value; + } + @NonNull public String getCategory() { return category; @@ -230,6 +245,7 @@ public boolean equals(Object o) { if (id != note.id) return false; if (accountId != note.accountId) return false; if (favorite != note.favorite) return false; + if (isSharedViaLink != note.isSharedViaLink) return false; if (scrollY != note.scrollY) return false; if (!Objects.equals(remoteId, note.remoteId)) return false; @@ -254,6 +270,7 @@ public int hashCode() { result = 31 * result + (modified != null ? modified.hashCode() : 0); result = 31 * result + content.hashCode(); result = 31 * result + (favorite ? 1 : 0); + result = 31 * result + (isSharedViaLink ? 1 : 0); result = 31 * result + (eTag != null ? eTag.hashCode() : 0); result = 31 * result + excerpt.hashCode(); result = 31 * result + scrollY; @@ -273,6 +290,7 @@ public String toString() { ", modified=" + modified + ", content='" + content + '\'' + ", favorite=" + favorite + + ", share_by_link=" + isSharedViaLink + ", eTag='" + eTag + '\'' + ", excerpt='" + excerpt + '\'' + ", scrollY=" + scrollY + diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 47fb3b56b..60e620a42 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -278,6 +278,8 @@ public void createPublicShareLink() { executorService.schedule(() -> { final var result = repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); if (result != null) { + note.setIsSharedViaLink(true); + repository.updateNote(note); runOnUiThread(this::recreate); } }, 0, TimeUnit.MICROSECONDS); @@ -313,15 +315,9 @@ private String createInternalLink() { @Override public void copyLink(OCShare share) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(share.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - ClipboardUtil.copyToClipboard(this, share.getShareLink()); - } + if (!note.isSharedViaLink()) { + return; } - */ if (TextUtils.isEmpty(share.getShareLink())) { copyAndShareFileLink(share.getShareLink()); @@ -506,7 +502,7 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { executorService.schedule(() -> { - final var result = repository.removeShare(share.getId()); + final var result = repository.removeShare(share, note); runOnUiThread(() -> { if (result) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt index b147b1e5c..480f3ef8e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt @@ -194,15 +194,19 @@ class ShareRepository(private val applicationContext: Context, private val accou } } - fun removeShare( - shareId: Long - ): Boolean { + fun removeShare(share: OCShare, note: Note): Boolean { val shareAPI = apiProvider.getShareAPI(applicationContext, account) return try { - val call = shareAPI.removeShare(shareId) + val call = shareAPI.removeShare(share.id) val response = call.execute() if (response.isSuccessful) { + + if (share.shareType == ShareType.PUBLIC_LINK) { + note.setIsSharedViaLink(false) + updateNote(note) + } + Log_OC.d(tag, "Share removed successfully.") } else { Log_OC.d(tag, "Failed to remove share: ${response.errorBody()?.string()}") @@ -232,6 +236,8 @@ class ShareRepository(private val applicationContext: Context, private val accou } } + fun updateNote(note: Note) = notesRepository.updateNote(note) + fun addShare( note: Note, shareType: ShareType, From a715566b3b19fe95f947280dd2dce9e002035812 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 6 Feb 2025 10:11:38 +0100 Subject: [PATCH 11/25] fix json parsing Signed-off-by: alperozturk --- .../notes/persistence/NotesRepository.java | 8 ++--- .../notes/persistence/dao/NoteDao.java | 18 ++++++------ .../notes/persistence/entity/Note.java | 29 +++++++++---------- .../notes/share/NoteShareActivity.java | 4 +-- .../notes/share/repository/ShareRepository.kt | 2 +- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java index 6f54244d2..fa9eb594d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java @@ -480,7 +480,7 @@ public NotesListWidgetData getNoteListWidgetData(int appWidgetId) { @NonNull @MainThread public LiveData addNoteAndSync(Account account, Note note) { - final var entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0, note.isSharedViaLink()); + final var entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0, note.isShared()); final var ret = new MutableLiveData(); executor.submit(() -> ret.postValue(addNote(account.getId(), entity))); return map(ret, newNote -> { @@ -508,7 +508,7 @@ public Note addNote(long accountId, @NonNull Note note) { @MainThread public LiveData moveNoteToAnotherAccount(Account account, @NonNull Note note) { - final var fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null, note.isSharedViaLink()); + final var fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null, note.isShared()); fullNote.setStatus(DBStatus.LOCAL_EDITED); deleteNoteAndSync(account, note.getId()); return addNoteAndSync(account, fullNote); @@ -570,7 +570,7 @@ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNo // https://github.com/nextcloud/notes-android/issues/1198 @Nullable final Long remoteId = db.getNoteDao().getRemoteId(oldNote.getId()); if (newContent == null) { - newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY(), oldNote.isSharedViaLink()); + newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY(), oldNote.isShared()); } else { final String title; if (newTitle != null) { @@ -584,7 +584,7 @@ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNo title = oldNote.getTitle(); } } - newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY(), oldNote.isSharedViaLink()); + newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY(), oldNote.isShared()); } int rows = db.getNoteDao().updateNote(newNote); // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly. diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java index 7b475c5c7..2f04c77e9 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java @@ -38,14 +38,14 @@ public interface NoteDao { String getNoteById = "SELECT * FROM NOTE WHERE id = :id"; String count = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId"; String countFavorites = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId AND favorite = 1"; - String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC"; - String searchRecentLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; - String searchFavoritesByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY modified DESC"; - String searchFavoritesLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY title COLLATE LOCALIZED ASC"; - String searchUncategorizedByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, modified DESC"; - String searchUncategorizedLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; - String searchCategoryByModified = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, modified DESC"; - String searchCategoryLexicographically = "SELECT id, remoteId, accountId, title, favorite, share_by_link, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, title COLLATE LOCALIZED ASC"; + String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC"; + String searchRecentLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; + String searchFavoritesByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY modified DESC"; + String searchFavoritesLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY title COLLATE LOCALIZED ASC"; + String searchUncategorizedByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, modified DESC"; + String searchUncategorizedLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; + String searchCategoryByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, modified DESC"; + String searchCategoryLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, title COLLATE LOCALIZED ASC"; @Query(getNoteById) LiveData getNoteById$(long id); @@ -141,7 +141,7 @@ public interface NoteDao { * Gets a list of {@link Note} objects with filled {@link Note#id} and {@link Note#remoteId}, * where {@link Note#remoteId} is not null */ - @Query("SELECT id, remoteId, 0 as accountId, '' as title, 0 as favorite, 0 as share_by_link, '' as excerpt, 0 as modified, '' as eTag, 0 as status, '' as category, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND remoteId IS NOT NULL") + @Query("SELECT id, remoteId, 0 as accountId, '' as title, 0 as favorite, 0 as isShared, '' as excerpt, 0 as modified, '' as eTag, 0 as status, '' as category, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND remoteId IS NOT NULL") List getRemoteIdAndId(long accountId); /** diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java index 7b8285670..84756ebbc 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java @@ -38,7 +38,7 @@ @Index(name = "IDX_NOTE_ACCOUNTID", value = "accountId"), @Index(name = "IDX_NOTE_CATEGORY", value = "category"), @Index(name = "IDX_NOTE_FAVORITE", value = "favorite"), - @Index(name = "IDX_NOTE_IS_SHARED_VIA_LINK", value = "share_by_link"), + @Index(name = "IDX_NOTE_IS_SHARED", value = "isShared"), @Index(name = "IDX_NOTE_MODIFIED", value = "modified"), @Index(name = "IDX_NOTE_REMOTEID", value = "remoteId"), @Index(name = "IDX_NOTE_STATUS", value = "status") @@ -83,9 +83,8 @@ public class Note implements Serializable, Item { private boolean favorite = false; @Expose - @ColumnInfo(defaultValue = "0", name = "share_by_link") - @SerializedName("share_by_link") - private boolean isSharedViaLink = false; + @ColumnInfo(defaultValue = "0") + private boolean isShared = false; @Expose @Nullable @@ -104,7 +103,7 @@ public Note() { } @Ignore - public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String eTag, boolean isSharedViaLink) { + public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String eTag, boolean isShared) { this.remoteId = remoteId; this.title = title; this.modified = modified; @@ -112,12 +111,12 @@ public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull Strin this.favorite = favorite; this.category = category; this.eTag = eTag; - this.isSharedViaLink = isSharedViaLink; + this.isShared = isShared; } @Ignore - public Note(long id, @Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String etag, @NonNull DBStatus status, long accountId, @NonNull String excerpt, int scrollY, boolean isSharedViaLink) { - this(remoteId, modified, title, content, category, favorite, etag, isSharedViaLink); + public Note(long id, @Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String etag, @NonNull DBStatus status, long accountId, @NonNull String excerpt, int scrollY, boolean isShared) { + this(remoteId, modified, title, content, category, favorite, etag, isShared); this.id = id; this.status = status; this.accountId = accountId; @@ -133,12 +132,12 @@ public void setId(long id) { this.id = id; } - public boolean isSharedViaLink() { - return isSharedViaLink; + public boolean isShared() { + return isShared; } - public void setIsSharedViaLink(boolean value) { - this.isSharedViaLink = value; + public void setIsShared(boolean value) { + this.isShared = value; } @NonNull @@ -245,7 +244,7 @@ public boolean equals(Object o) { if (id != note.id) return false; if (accountId != note.accountId) return false; if (favorite != note.favorite) return false; - if (isSharedViaLink != note.isSharedViaLink) return false; + if (isShared != note.isShared) return false; if (scrollY != note.scrollY) return false; if (!Objects.equals(remoteId, note.remoteId)) return false; @@ -270,7 +269,7 @@ public int hashCode() { result = 31 * result + (modified != null ? modified.hashCode() : 0); result = 31 * result + content.hashCode(); result = 31 * result + (favorite ? 1 : 0); - result = 31 * result + (isSharedViaLink ? 1 : 0); + result = 31 * result + (isShared ? 1 : 0); result = 31 * result + (eTag != null ? eTag.hashCode() : 0); result = 31 * result + excerpt.hashCode(); result = 31 * result + scrollY; @@ -290,7 +289,7 @@ public String toString() { ", modified=" + modified + ", content='" + content + '\'' + ", favorite=" + favorite + - ", share_by_link=" + isSharedViaLink + + ", isShared=" + isShared + ", eTag='" + eTag + '\'' + ", excerpt='" + excerpt + '\'' + ", scrollY=" + scrollY + diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 60e620a42..0401416eb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -278,7 +278,7 @@ public void createPublicShareLink() { executorService.schedule(() -> { final var result = repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); if (result != null) { - note.setIsSharedViaLink(true); + note.setIsShared(true); repository.updateNote(note); runOnUiThread(this::recreate); } @@ -315,7 +315,7 @@ private String createInternalLink() { @Override public void copyLink(OCShare share) { - if (!note.isSharedViaLink()) { + if (!note.isShared()) { return; } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt index 480f3ef8e..f62652193 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/repository/ShareRepository.kt @@ -203,7 +203,7 @@ class ShareRepository(private val applicationContext: Context, private val accou if (response.isSuccessful) { if (share.shareType == ShareType.PUBLIC_LINK) { - note.setIsSharedViaLink(false) + note.setIsShared(false) updateNote(note) } From b71874ba5ad99a123a86fd1c2a1c5c1f936e77ae Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 14 Feb 2025 10:15:37 +0100 Subject: [PATCH 12/25] fix git conflicts Signed-off-by: alperozturk --- app/build.gradle | 4 +- .../notes/persistence/dao/ShareDao.kt | 10 +- .../notes/share/ClientFactoryImpl.java | 84 --- .../share/CreateShareViaLinkOperation.java | 91 --- .../notes/share/NoteShareFragment.java | 692 ------------------ .../owncloud/notes/share/SyncOperation.java | 37 - .../owncloud/notes/shared/server/Server.kt | 40 - .../owncloud/notes/shared/user/User.kt | 56 -- .../notes/shared/util/ClipboardUtil.kt | 50 -- app/src/main/res/layout/activity_row.xml | 2 +- app/src/main/res/values/strings.xml | 76 +- gradle/verification-metadata.xml | 17 + 12 files changed, 78 insertions(+), 1081 deletions(-) delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt diff --git a/app/build.gradle b/app/build.gradle index 513be6a83..088e7628a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -122,7 +122,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation 'androidx.fragment:fragment:1.8.5' + implementation 'androidx.fragment:fragment:1.8.6' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7' implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0' @@ -139,7 +139,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.11.0' // Gson - implementation 'com.google.code.gson:gson:2.11.0' + implementation 'com.google.code.gson:gson:2.12.1' // ReactiveX implementation 'io.reactivex.rxjava2:rxjava:2.2.21' diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt index 97f27cac0..15ab98f80 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt @@ -2,15 +2,15 @@ package it.niedermann.owncloud.notes.persistence.dao import androidx.room.Dao import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import it.niedermann.owncloud.notes.persistence.entity.ShareEntity @Dao interface ShareDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addShareEntities(entities: List) - @Insert - fun addShareEntity(entity: ShareEntity) - - @Query("SELECT * FROM share_table WHERE noteRemoteId = :noteRemoteId AND userName = :userName") - fun getShareEntities(noteRemoteId: Long, userName: String): List + @Query("SELECT * FROM share_table WHERE path = :path") + fun getShareEntities(path: String): List } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java b/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java deleted file mode 100644 index b9c82d25e..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ - -package it.niedermann.owncloud.notes.share.operations; - -import android.accounts.Account; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.app.Activity; -import android.content.Context; -import android.net.Uri; - -import com.nextcloud.common.NextcloudClient; -import com.nextcloud.common.PlainClient; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; -import com.owncloud.android.lib.common.accounts.AccountUtils; - -import java.io.IOException; - -import it.niedermann.owncloud.notes.shared.user.User; - -public class ClientFactoryImpl implements ClientFactory { - - private Context context; - - public ClientFactoryImpl(Context context) { - this.context = context; - } - - @Override - public OwnCloudClient create(User user) throws CreationException { - try { - return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context); - } catch (OperationCanceledException | - AuthenticatorException | - IOException e) { - throw new CreationException(e); - } - } - - @Override - public NextcloudClient createNextcloudClient(User user) throws CreationException { - try { - return OwnCloudClientFactory.createNextcloudClient(user, context); - } catch (AccountUtils.AccountNotFoundException e) { - throw new CreationException(e); - } - } - - @Override - public OwnCloudClient create(Account account) - throws OperationCanceledException, AuthenticatorException, IOException, - AccountUtils.AccountNotFoundException { - return OwnCloudClientFactory.createOwnCloudClient(account, context); - } - - @Override - public OwnCloudClient create(Account account, Activity currentActivity) - throws OperationCanceledException, AuthenticatorException, IOException, - AccountUtils.AccountNotFoundException { - return OwnCloudClientFactory.createOwnCloudClient(account, context, currentActivity); - } - - @Override - public OwnCloudClient create(Uri uri, boolean followRedirects, boolean useNextcloudUserAgent) { - return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); - } - - @Override - public OwnCloudClient create(Uri uri, boolean followRedirects) { - return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); - } - - @Override - public PlainClient createPlainClient() { - return new PlainClient(context); - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java deleted file mode 100644 index 509fce819..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java +++ /dev/null @@ -1,91 +0,0 @@ -package it.niedermann.owncloud.notes.share; - -import java.util.ArrayList; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.FileUtils; -import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; -import com.owncloud.android.lib.resources.shares.OCShare; -import com.owncloud.android.lib.resources.shares.ShareType; - - -import it.niedermann.owncloud.notes.shared.user.User; - -/** - * Creates a new public share for a given file - */ -public class CreateShareViaLinkOperation extends SyncOperation { - - private final String path; - private final String password; - private int permissions = OCShare.NO_PERMISSION; - - public CreateShareViaLinkOperation(String path, String password, User user) { - super(user); - - this.path = path; - this.password = password; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path, - ShareType.PUBLIC_LINK, - "", - false, - password, - permissions); - createOp.setGetShareDetails(true); - RemoteOperationResult result = createOp.execute(client); - - if (result.isSuccess()) { - if (result.getData().size() > 0) { - Object item = result.getData().get(0); - if (item instanceof OCShare) { - updateData((OCShare) item); - } else { - ArrayList data = result.getData(); - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); - result.setData(data); - } - } else { - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); - } - } - - return result; - } - - private void updateData(OCShare share) { - // Update DB with the response - share.setPath(path); - if (path.endsWith(FileUtils.PATH_SEPARATOR)) { - share.setFolder(true); - } else { - share.setFolder(false); - } - - // TODO: Save share - - /* - getStorageManager().saveShare(share); - - // Update OCFile with data from share: ShareByLink and publicLink - OCFile file = getStorageManager().getFileByEncryptedRemotePath(path); - if (file != null) { - file.setSharedViaLink(true); - getStorageManager().saveFile(file); - } - */ - - } - - public String getPath() { - return this.path; - } - - public String getPassword() { - return this.password; - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java deleted file mode 100644 index 759c8a8f0..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java +++ /dev/null @@ -1,692 +0,0 @@ -package it.niedermann.owncloud.notes.share; - -import android.Manifest; -import android.app.Activity; -import android.app.SearchManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -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.inputmethod.EditorInfo; - -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.core.content.ContextCompat; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.google.android.material.snackbar.Snackbar; -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 java.util.ArrayList; -import java.util.List; - -import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; -import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.branding.BrandedSnackbar; -import it.niedermann.owncloud.notes.databinding.FragmentNoteShareBinding; -import it.niedermann.owncloud.notes.persistence.entity.Account; -import it.niedermann.owncloud.notes.persistence.entity.Note; -import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; -import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; -import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; -import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; -import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; -import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; -import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; -import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; -import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; -import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; -import it.niedermann.owncloud.notes.shared.user.User; -import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; - -public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { - - private static final String TAG = "NoteShareFragment"; - private static final String ARG_NOTE = "NOTE"; - private static final String ARG_ACCOUNT = "ACCOUNT"; - private static final String ARG_USER = "USER"; - public static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; - - private FragmentNoteShareBinding binding; - private Note note; - private User user; - private Account account; - - private OnEditShareListener onEditShareListener; - private ClientFactoryImpl clientFactory; - - public static NoteShareFragment newInstance(Note note, User user, Account account) { - NoteShareFragment fragment = new NoteShareFragment(); - Bundle args = new Bundle(); - args.putSerializable(ARG_NOTE, note); - args.putSerializable(ARG_ACCOUNT, account); - args.putParcelable(ARG_USER, user); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - clientFactory = new ClientFactoryImpl(requireContext()); - - if (savedInstanceState != null) { - note = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_NOTE, Note.class); - account = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_ACCOUNT, Account.class); - user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_USER, User.class); - } else { - Bundle arguments = getArguments(); - if (arguments != null) { - note = BundleExtensionsKt.getSerializableArgument(arguments, ARG_NOTE, Note.class); - account = BundleExtensionsKt.getSerializableArgument(arguments, ARG_ACCOUNT, Account.class); - user = BundleExtensionsKt.getParcelableArgument(arguments, ARG_USER, User.class); - } - } - - if (note == null) { - throw new IllegalArgumentException("Note cannot be null"); - } - - if (user == null) { - throw new IllegalArgumentException("Account cannot be null"); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - refreshCapabilitiesFromDB(); - refreshSharesFromDB(); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentNoteShareBinding.inflate(inflater, container, false); - - binding.sharesList.setAdapter(new ShareeListAdapter(requireActivity(), - new ArrayList<>(), - this, - user, - account)); - - binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); - - binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); - - setupView(); - - return binding.getRoot(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - try { - onEditShareListener = (OnEditShareListener) context; - } catch (Exception e) { - throw new IllegalArgumentException("Calling activity must implement the interface", e); - } - } - - @Override - public void onStart() { - super.onStart(); - UsersAndGroupsSearchConfig.INSTANCE.setSearchOnlyUsers(true); - } - - @Override - public void onStop() { - super.onStop(); - UsersAndGroupsSearchConfig.INSTANCE.reset(); - } - - private void setupView() { - setShareWithYou(); - - // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); - - setupSearchView((SearchManager) requireContext().getSystemService(Context.SEARCH_SERVICE), binding.searchView, requireActivity().getComponentName()); - // viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); - - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - - /* - if (file.canReshare()) { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); - } else { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - } - */ - - } - - private void setupSearchView(@Nullable SearchManager searchManager, SearchView searchView, - ComponentName componentName) { - if (searchManager == null) { - searchView.setVisibility(View.GONE); - return; - } - - // assumes parent activity is the searchable activity - searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)); - - // do not iconify the widget; expand it by default - searchView.setIconifiedByDefault(false); - - // avoid fullscreen with softkeyboard - searchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); - - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - // return true to prevent the query from being processed; - return true; - } - - @Override - public boolean onQueryTextChange(String newText) { - // leave it for the parent listener in the hierarchy / default behaviour - return false; - } - }); - } - - private void disableSearchView(View view) { - view.setEnabled(false); - - if (view instanceof ViewGroup viewGroup) { - for (int i = 0; i < viewGroup.getChildCount(); i++) { - disableSearchView(viewGroup.getChildAt(i)); - } - } - } - - // TODO: Check if note.getAccountId() return note's owner's id - private boolean accountOwnsFile() { - String noteId = String.valueOf(note.getAccountId()); - return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); - } - - private void setShareWithYou() { - if (accountOwnsFile()) { - binding.sharedWithYouContainer.setVisibility(View.GONE); - } else { - - /* - // TODO: How to get owner display name from note? - - binding.sharedWithYouUsername.setText( - String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); - */ - - - Glide.with(this) - .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) - .placeholder(R.drawable.ic_account_circle_grey_24dp) - .error(R.drawable.ic_account_circle_grey_24dp) - .apply(RequestOptions.circleCropTransform()) - .into(binding.sharedWithYouAvatar); - - binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); - - /* - // TODO: Note's note? - String note = file.getNote(); - - if (!TextUtils.isEmpty(note)) { - binding.sharedWithYouNote.setText(file.getNote()); - binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); - } else { - binding.sharedWithYouNoteContainer.setVisibility(View.GONE); - } - */ - } - } - - public void copyInternalLink() { - if (account == null) { - BrandedSnackbar.make(requireView(), getString(R.string.note_share_fragment_could_not_retrieve_url), Snackbar.LENGTH_LONG) - .setAnchorView(binding.sharesList) - .show(); - return; - } - - showShareLinkDialog(); - } - - private void showShareLinkDialog() { - String link = createInternalLink(); - - Intent intentToShareLink = new Intent(Intent.ACTION_SEND); - - intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); - intentToShareLink.setType("text/plain"); - intentToShareLink.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.note_share_fragment_subject_shared_with_you, note.getTitle())); - - String[] packagesToExclude = new String[] { requireContext().getPackageName() }; - DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); - chooserDialog.show(getParentFragmentManager(), FTAG_CHOOSER_DIALOG); - } - - // TODO: Check account.getUrl returning base url? - private String createInternalLink() { - Uri baseUri = Uri.parse(account.getUrl()); - return baseUri + "/index.php/f/" + note.getId(); - } - - // TODO: Capabilities in notes app doesn't have following functions... - public void createPublicShareLink() { - /* - if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - - } else { - // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note - // Is CreateShareViaLink operation compatible? - - Intent service = new Intent(fileActivity, OperationsService.class); - service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); - service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); - if (!TextUtils.isEmpty(password)) { - service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); - } - service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); - mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); - } - */ - - } - - public void createSecureFileDrop() { - // fileOperationsHelper.shareFolderViaSecureFileDrop(file); - } - - /* - // TODO: Cant call getFileWithLink - - public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { - List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - if (shares.size() == SINGLE_LINK_SIZE) { - FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); - } else { - if (fileActivity instanceof FileDisplayActivity) { - ((FileDisplayActivity) fileActivity).showDetails(file, 1); - } else { - showShareFile(file); - } - } - - fileActivity.refreshList(); - } - */ - private void showSendLinkTo(OCShare publicShare) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(publicShare.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - // TODO: get link from public share and pass to the function - showShareLinkDialog(); - } - } - */ - - } - - public void copyLink(OCShare share) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(share.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); - } - } - */ - - } - - @Override - public void showSharingMenuActionSheet(OCShare share) { - if (getActivity() != null && !getActivity().isFinishing()) { - new FileDetailSharingMenuBottomSheetDialog(getActivity(), this, share).show(); - } - } - - @Override - public void showPermissionsDialog(OCShare share) { - new QuickSharingPermissionsBottomSheetDialog(getActivity(), this, share).show(); - } - - public void onUpdateShareInformation(RemoteOperationResult result) { - if (result.isSuccess()) { - refreshUiFromDB(); - } else { - setupView(); - } - } - - /** - * Get {@link OCShare} instance from DB and updates the UI. - */ - private void refreshUiFromDB() { - refreshSharesFromDB(); - // Updates UI with new state - setupView(); - } - - private void unshareWith(OCShare share) { - // fileOperationsHelper.unshareShare(file, share); - } - - /** - * 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(note, - 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 - public void showProfileBottomSheet(User user, String shareWith) { - if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - new RetrieveHoverCardAsyncTask(user, - account, - shareWith, - getActivity(), - clientFactory).execute(); - } - } - - /** - * Get known server capabilities from DB - */ - public void refreshCapabilitiesFromDB() { - // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); - } - - /** - * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities - * before reading database. - */ - public void refreshSharesFromDB() { - /* - OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); - if (newFile != null) { - file = newFile; - } - - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - - if (adapter == null) { - BrandedSnackbar.make(requireView(), getString(R.string.could_not_retrieve_shares), Snackbar.LENGTH_LONG) - .show(); - return; - } - adapter.getShares().clear(); - - // to show share with users/groups info - List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), - user.getAccountName()); - - adapter.addShares(shares); - - if (FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities) || !file.canReshare()) { - return; - } - - // Get public share - List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) { - final OCShare ocShare = new OCShare(); - ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); - publicShares.add(ocShare); - } else { - adapter.removeNewPublicShare(); - } - - adapter.addShares(publicShares); - */ - - } - - private void checkContactPermission() { - if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { - pickContactEmail(); - } else { - requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); - } - } - - private void pickContactEmail() { - Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); - - if (intent.resolveActivity(requireContext().getPackageManager()) != null) { - onContactSelectionResultLauncher.launch(intent); - } else { - BrandedSnackbar.make(requireView(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message), Snackbar.LENGTH_LONG) - .show(); - } - } - - private void handleContactResult(@NonNull Uri contactUri) { - // Define the projection to get all email addresses. - String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; - - Cursor cursor = requireActivity().getContentResolver().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); - 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(() -> { - binding.searchView.setQuery(email, false); - binding.searchView.requestFocus(); - }); - } else { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address."); - } - } else { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); - } - cursor.close(); - } else { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); - } - } - - private boolean containsNoNewPublicShare(List shares) { - for (OCShare share : shares) { - if (share.getShareType() == ShareType.NEW_PUBLIC_LINK) { - return false; - } - } - - return true; - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putSerializable(ARG_NOTE, note); - outState.putSerializable(ARG_ACCOUNT, account); - outState.putParcelable(ARG_USER, user); - } - - public void avatarGenerated(Drawable avatarDrawable, Object callContext) { - binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); - } - - public boolean shouldCallGeneratedCallback(String tag, Object callContext) { - return false; - } - - private boolean isReshareForbidden(OCShare share) { - return false; - // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); - } - - @VisibleForTesting - public void search(String query) { - SearchView searchView = requireView().findViewById(R.id.searchView); - searchView.setQuery(query, true); - } - - @Override - public void advancedPermissions(OCShare share) { - // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); - } - - - @Override - public void sendNewEmail(OCShare share) { - // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); - } - - @Override - public void unShare(OCShare share) { - unshareWith(share); - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - if (adapter == null) { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - return; - } - adapter.remove(share); - } - - @Override - public void sendLink(OCShare share) { - /* - if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { - FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); - } else { - showSendLinkTo(share); - } - */ - - } - - @Override - public void addAnotherLink(OCShare share) { - createPublicShareLink(); - } - - private void modifyExistingShare(OCShare share, int screenTypePermission) { - // onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); - } - - @Override - public void onQuickPermissionChanged(OCShare share, int permission) { - // fileOperationsHelper.setPermissionsToShare(share, permission); - } - - //launcher for contact permission - private final ActivityResultLauncher requestContactPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (isGranted) { - pickContactEmail(); - } else { - BrandedSnackbar.make(binding.getRoot(), getString(R.string.contact_no_permission), Snackbar.LENGTH_LONG) - .show(); - } - }); - - //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) { - BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - return; - } - - Uri contactUri = intent.getData(); - if (contactUri == null) { - BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - return; - } - - handleContactResult(contactUri); - - } - }); - - public interface OnEditShareListener { - void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown, - boolean isExpiryDateShown); - - void onShareProcessClosed(); - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java deleted file mode 100644 index 62eb8661a..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java +++ /dev/null @@ -1,37 +0,0 @@ -package it.niedermann.owncloud.notes.share; - - -import android.content.Context; -import android.os.Handler; - -import com.nextcloud.common.NextcloudClient; -import com.nextcloud.common.User; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; - -import androidx.annotation.NonNull; - -public abstract class SyncOperation extends RemoteOperation { - private final User user; - - public SyncOperation(@NonNull User user) { - this.user = user; - } - - public RemoteOperationResult execute(Context context) { - return super.execute(user, context); - } - - public RemoteOperationResult execute(@NonNull NextcloudClient client) { - return run(client); - } - - public Thread execute(OwnCloudClient client, - OnRemoteOperationListener listener, - Handler listenerHandler) { - return super.execute(client, listener, listenerHandler); - } - -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt deleted file mode 100644 index be773c7f7..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package it.niedermann.owncloud.notes.shared.server - -import android.os.Parcel -import android.os.Parcelable -import com.owncloud.android.lib.resources.status.OwnCloudVersion -import java.net.URI - -/** - * This object provides all information necessary to interact - * with backend server. - */ -data class Server(val uri: URI, val version: OwnCloudVersion) : Parcelable { - - constructor(source: Parcel) : this( - source.readSerializable() as URI, - source.readParcelable(OwnCloudVersion::class.java.classLoader) as OwnCloudVersion - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { - writeSerializable(uri) - writeParcelable(version, 0) - } - - companion object { - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): Server = Server(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt deleted file mode 100644 index b68c4598d..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2020 Chris Narkiewicz - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package it.niedermann.owncloud.notes.shared.user - -import android.accounts.Account -import android.os.Parcelable -import com.owncloud.android.lib.common.OwnCloudAccount -import it.niedermann.owncloud.notes.shared.server.Server - -interface User : Parcelable, com.nextcloud.common.User { - override val accountName: String - val server: Server - - /** - * This is temporary helper method created to facilitate incremental refactoring. - * Code using legacy platform Account can be partially converted to instantiate User - * object and use account instance when required. - * - * This method calls will allow tracing code awaiting further refactoring. - * - * @return Account instance that is associated with this User object. - */ - @Deprecated("Temporary workaround") - override fun toPlatformAccount(): Account - - /** - * This is temporary helper method created to facilitate incremental refactoring. - * Code using legacy ownCloud account can be partially converted to instantiate User - * object and use account instance when required. - * - * This method calls will allow tracing code awaiting further refactoring. - * - * @return OwnCloudAccount instance that is associated with this User object. - */ - @Deprecated("Temporary workaround") - fun toOwnCloudAccount(): OwnCloudAccount - - /** - * Compare account names, case insensitive. - * - * @return true if account names are same, false otherwise - */ - fun nameEquals(user: User?): Boolean - - /** - * Compare account names, case insensitive. - * - * @return true if account names are same, false otherwise - */ - fun nameEquals(accountName: CharSequence?): Boolean -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt deleted file mode 100644 index cb6c5e14f..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2018 Andy Scherzinger - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ - -package it.niedermann.owncloud.notes.shared.util - -import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.text.TextUtils -import android.widget.Toast -import com.owncloud.android.lib.common.utils.Log_OC -import it.niedermann.owncloud.notes.R - -/** - * Helper implementation to copy a string into the system clipboard. - */ -object ClipboardUtil { - private val TAG = ClipboardUtil::class.java.name - - @JvmStatic - @JvmOverloads - @Suppress("TooGenericExceptionCaught") - fun copyToClipboard(activity: Activity, text: String?, showToast: Boolean = true) { - if (!TextUtils.isEmpty(text)) { - try { - val clip = ClipData.newPlainText( - activity.getString( - R.string.clipboard_label, - activity.getString(R.string.app_name) - ), - text - ) - (activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(clip) - if (showToast) { - Toast.makeText(activity, R.string.clipboard_text_copied, Toast.LENGTH_SHORT).show() - } - } catch (e: Exception) { - Toast.makeText(activity, R.string.clipboard_unexpected_error, Toast.LENGTH_SHORT).show() - Log_OC.e(TAG, "Exception caught while copying to clipboard", e) - } - } else { - Toast.makeText(activity, R.string.clipboard_no_text_to_copy, Toast.LENGTH_SHORT).show() - } - } -} diff --git a/app/src/main/res/layout/activity_row.xml b/app/src/main/res/layout/activity_row.xml index 8d6e4d150..ffb8e2e6c 100644 --- a/app/src/main/res/layout/activity_row.xml +++ b/app/src/main/res/layout/activity_row.xml @@ -23,7 +23,7 @@ android:layout_height="@dimen/default_icon_size" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/spacer_2x" - android:contentDescription="@string/note_share_fragment_activity_icon_content_description" /> + android:contentDescription="@string/note_share_activity_activity_icon_content_description" /> Choose a category - + + com.nextcloud.android.providers.UsersAndGroupsSearchProvider + com.nextcloud.android.providers.UsersAndGroupsSearchProvider.action.SHARE_WITH + + + @string/link_share_view_only @string/link_share_allow_upload_and_editing @string/link_share_file_drop - + @string/link_share_view_only @string/link_share_editing - note activity icon - note share icon - note share external icon - note share copy icon - note share more icon - note share icon - note share user icon - note share contact icon - Shared with you by %1$s - Name, Federated Cloud ID or email address… - Share link - Policy or permissions prevent resharing - Could not retrieve URL - \"%1$s\" has been shared with you - Contact permission is required. - You must enter a password - Password - Enter an optional password - Skip - Cancel - + + + note activity icon + note share icon + note share external icon + note share copy icon + note share more icon + note share icon + note share user icon + note share contact icon + Shared with you by %1$s + Share note + Name, Federated Cloud ID or email address… + Share link + Policy or permissions prevent resharing + Could not retrieve URL + \"%1$s\" has been shared with you + Contact permission is required. + + + Advanced Settings + Hide download + Note to recipient + Note + Next + Share and Copy Link + Confirm + Set Note + Send Share + Name + Link Name + You must enter a password + Password + Please select at least one permission to share. + Label cannot be empty + Cancel + Failed to create a share + + + Enter an optional password + Skip + Enter a password OK Delete @@ -76,6 +102,7 @@ No App available to handle mail address No actions for this user + Failed to remove share Failed to pick email address. Failed to update UI No app available to select contacts @@ -124,6 +151,9 @@ %1$s (remote) %1$s (conversation) on %1$s + (remote) + + Copy link Share diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 07f1c940a..5a692abf7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -9,6 +9,7 @@ + @@ -1197,6 +1198,14 @@ + + + + + + + + @@ -1210,6 +1219,14 @@ + + + + + + + + From 9cbae8ad2e7205a23af182812b9a27b61669d8ff Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 27 Jan 2025 12:05:17 +0100 Subject: [PATCH 13/25] add some logic Signed-off-by: alperozturk --- app/build.gradle | 13 +- .../share/CreateShareViaLinkOperation.java | 91 +++ .../notes/share/NoteShareFragment.java | 647 ++++++++++++++++++ .../owncloud/notes/share/SyncOperation.java | 37 + .../owncloud/notes/shared/server/Server.kt | 40 ++ .../owncloud/notes/shared/user/User.kt | 56 ++ .../notes/shared/util/ClipboardUtil.kt | 50 ++ app/src/main/res/values/strings.xml | 128 +--- gradle/verification-metadata.xml | 47 +- 9 files changed, 937 insertions(+), 172 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt diff --git a/app/build.gradle b/app/build.gradle index 088e7628a..c18c1a1cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,17 +95,12 @@ ext { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' - implementation 'com.google.guava:guava:31.1-android' - implementation ('commons-httpclient:commons-httpclient:3.1') { - exclude group: 'commons-logging', module: 'commons-logging' - } - - implementation("com.github.nextcloud:android-library:2.19.0") { + implementation("com.github.nextcloud:android-library:3d422b28376339c0fbd772e480dbbdc56b7ae1a1") { exclude group: 'org.ogce', module: 'xpp3' } // Nextcloud SSO - implementation 'com.github.nextcloud.android-common:ui:0.23.2' + implementation 'com.github.nextcloud.android-common:ui:0.24.0' implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.2' implementation 'com.github.stefan-niedermann:android-commons:1.0.2' implementation "com.github.stefan-niedermann.nextcloud-commons:sso-glide:$commonsVersion" @@ -122,7 +117,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.core:core-splashscreen:1.0.1' - implementation 'androidx.fragment:fragment:1.8.6' + implementation 'androidx.fragment:fragment:1.8.5' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7' implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0' @@ -139,7 +134,7 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.11.0' // Gson - implementation 'com.google.code.gson:gson:2.12.1' + implementation 'com.google.code.gson:gson:2.11.0' // ReactiveX implementation 'io.reactivex.rxjava2:rxjava:2.2.21' diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java new file mode 100644 index 000000000..509fce819 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java @@ -0,0 +1,91 @@ +package it.niedermann.owncloud.notes.share; + +import java.util.ArrayList; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.ShareType; + + +import it.niedermann.owncloud.notes.shared.user.User; + +/** + * Creates a new public share for a given file + */ +public class CreateShareViaLinkOperation extends SyncOperation { + + private final String path; + private final String password; + private int permissions = OCShare.NO_PERMISSION; + + public CreateShareViaLinkOperation(String path, String password, User user) { + super(user); + + this.path = path; + this.password = password; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path, + ShareType.PUBLIC_LINK, + "", + false, + password, + permissions); + createOp.setGetShareDetails(true); + RemoteOperationResult result = createOp.execute(client); + + if (result.isSuccess()) { + if (result.getData().size() > 0) { + Object item = result.getData().get(0); + if (item instanceof OCShare) { + updateData((OCShare) item); + } else { + ArrayList data = result.getData(); + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); + result.setData(data); + } + } else { + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); + } + } + + return result; + } + + private void updateData(OCShare share) { + // Update DB with the response + share.setPath(path); + if (path.endsWith(FileUtils.PATH_SEPARATOR)) { + share.setFolder(true); + } else { + share.setFolder(false); + } + + // TODO: Save share + + /* + getStorageManager().saveShare(share); + + // Update OCFile with data from share: ShareByLink and publicLink + OCFile file = getStorageManager().getFileByEncryptedRemotePath(path); + if (file != null) { + file.setSharedViaLink(true); + getStorageManager().saveFile(file); + } + */ + + } + + public String getPath() { + return this.path; + } + + public String getPassword() { + return this.password; + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java new file mode 100644 index 000000000..5f1004d38 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java @@ -0,0 +1,647 @@ +package it.niedermann.owncloud.notes.share; + +import android.Manifest; +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 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.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.snackbar.Snackbar; + +import java.util.ArrayList; +import java.util.List; + +import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; +import it.niedermann.owncloud.notes.R; +import it.niedermann.owncloud.notes.branding.BrandedSnackbar; +import it.niedermann.owncloud.notes.databinding.FragmentNoteShareBinding; +import it.niedermann.owncloud.notes.persistence.entity.Account; +import it.niedermann.owncloud.notes.persistence.entity.Note; +import it.niedermann.owncloud.notes.shared.user.User; +import it.niedermann.owncloud.notes.shared.util.ClipboardUtil; +import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; + +public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, + DisplayUtils.AvatarGenerationListener, + Injectable, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { + + private static final String TAG = "NoteShareFragment"; + private static final String ARG_NOTE = "NOTE"; + private static final String ARG_ACCOUNT = "ACCOUNT"; + private static final String ARG_USER = "USER"; + + private FragmentNoteShareBinding binding; + private Note note; + private User user; + private Account account; + + private OnEditShareListener onEditShareListener; + + public static NoteShareFragment newInstance(Note note, User user, Account account) { + NoteShareFragment fragment = new NoteShareFragment(); + Bundle args = new Bundle(); + args.putSerializable(ARG_NOTE, note); + args.putSerializable(ARG_ACCOUNT, account); + args.putParcelable(ARG_USER, user); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState != null) { + note = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_NOTE, Note.class); + account = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_ACCOUNT, Account.class); + user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_USER, User.class); + } else { + Bundle arguments = getArguments(); + if (arguments != null) { + note = BundleExtensionsKt.getSerializableArgument(arguments, ARG_NOTE, Note.class); + account = BundleExtensionsKt.getSerializableArgument(arguments, ARG_ACCOUNT, Account.class); + user = BundleExtensionsKt.getParcelableArgument(arguments, ARG_USER, User.class); + } + } + + if (note == null) { + throw new IllegalArgumentException("Note cannot be null"); + } + + if (user == null) { + throw new IllegalArgumentException("Account cannot be null"); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + refreshCapabilitiesFromDB(); + refreshSharesFromDB(); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = FragmentNoteShareBinding.inflate(inflater, container, false); + + binding.sharesList.setAdapter(new ShareeListAdapter(fileActivity, + new ArrayList<>(), + this, + user, + note)); + + binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); + + binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); + + setupView(); + + return binding.getRoot(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + 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 setupView() { + setShareWithYou(); + + OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + + FileDetailSharingFragmentHelper.setupSearchView( + (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), + binding.searchView, + fileActivity.getComponentName()); + viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); + + if (file.canReshare()) { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); + } else { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + } + } + + private void disableSearchView(View view) { + view.setEnabled(false); + + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + disableSearchView(viewGroup.getChildAt(i)); + } + } + } + + // TODO: Check if note.getAccountId() return note's owner's id + private boolean accountOwnsFile() { + String noteId = String.valueOf(note.getAccountId()); + return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); + } + + private void setShareWithYou() { + if (accountOwnsFile()) { + binding.sharedWithYouContainer.setVisibility(View.GONE); + } else { + + /* + // TODO: How to get owner display name from note? + + binding.sharedWithYouUsername.setText( + String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); + */ + + + Glide.with(this) + .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) + .placeholder(R.drawable.ic_account_circle_grey_24dp) + .error(R.drawable.ic_account_circle_grey_24dp) + .apply(RequestOptions.circleCropTransform()) + .into(binding.sharedWithYouAvatar); + + binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); + + /* + // TODO: Note's note? + String note = file.getNote(); + + if (!TextUtils.isEmpty(note)) { + binding.sharedWithYouNote.setText(file.getNote()); + binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); + } else { + binding.sharedWithYouNoteContainer.setVisibility(View.GONE); + } + */ + } + } + + private void copyInternalLink() { + if (account == null) { + BrandedSnackbar.make(requireView(), getString(R.string.note_share_fragment_could_not_retrieve_url), Snackbar.LENGTH_LONG) + .setAnchorView(binding.sharesList) + .show(); + return; + } + + showShareLinkDialog(); + } + + private void showShareLinkDialog() { + String link = createInternalLink(); + + Intent intentToShareLink = new Intent(Intent.ACTION_SEND); + + intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); + intentToShareLink.setType("text/plain"); + intentToShareLink.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.note_share_fragment_subject_shared_with_you, note.getTitle())); + + String[] packagesToExclude = new String[] { requireContext().getPackageName() }; + DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); + chooserDialog.show(getParentFragmentManager(), FileDisplayActivity.FTAG_CHOOSER_DIALOG); + } + + // TODO: Check account.getUrl returning base url? + private String createInternalLink() { + Uri baseUri = Uri.parse(account.getUrl()); + return baseUri + "/index.php/f/" + note.getId(); + } + + // TODO: Capabilities in notes app doesn't have following functions... + public void createPublicShareLink() { + if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); + + } else { + // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note + // Is CreateShareViaLink operation compatible? + + Intent service = new Intent(fileActivity, OperationsService.class); + service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); + service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); + if (!TextUtils.isEmpty(password)) { + service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); + } + service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); + } + } + + private void createSecureFileDrop() { + fileOperationsHelper.shareFolderViaSecureFileDrop(file); + } + + /* + // TODO: Cant call getFileWithLink + + public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { + List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + if (shares.size() == SINGLE_LINK_SIZE) { + FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); + } else { + if (fileActivity instanceof FileDisplayActivity) { + ((FileDisplayActivity) fileActivity).showDetails(file, 1); + } else { + showShareFile(file); + } + } + + fileActivity.refreshList(); + } + */ + private void showSendLinkTo(OCShare publicShare) { + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(publicShare.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + // TODO: get link from public share and pass to the function + showShareLinkDialog(); + } + } + } + + public void copyLink(OCShare share) { + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(share.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + } + } + } + + /** + * 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).show(); + } + } + + /** + * show quick sharing permission dialog + * + * @param share + */ + @Override + public void showPermissionsDialog(OCShare share) { + new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils).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) { + if (result.isSuccess()) { + refreshUiFromDB(); + } else { + setupView(); + } + } + + /** + * Get {@link OCShare} instance from DB and updates the UI. + */ + private void refreshUiFromDB() { + refreshSharesFromDB(); + // Updates UI with new state + setupView(); + } + + private void unshareWith(OCShare share) { + fileOperationsHelper.unshareShare(file, share); + } + + /** + * 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); + } + + @Override + public void requestPasswordForShare(OCShare share, boolean askForPassword) { + SharePasswordDialogFragment dialog = SharePasswordDialogFragment.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(); + } + } + + /** + * Get known server capabilities from DB + */ + public void refreshCapabilitiesFromDB() { + capabilities = fileDataStorageManager.getCapability(user.getAccountName()); + } + + /** + * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities + * before reading database. + */ + public void refreshSharesFromDB() { + OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); + if (newFile != null) { + file = newFile; + } + + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + + if (adapter == null) { + DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); + return; + } + adapter.getShares().clear(); + + // to show share with users/groups info + List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), + user.getAccountName()); + + adapter.addShares(shares); + + if (FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities) || !file.canReshare()) { + return; + } + + // Get public share + List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares()) && + (!file.isEncrypted() || capabilities.getEndToEndEncryption().isTrue())) { + final OCShare ocShare = new OCShare(); + ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); + publicShares.add(ocShare); + } else { + adapter.removeNewPublicShare(); + } + + adapter.addShares(publicShares); + } + + private void checkContactPermission() { + if (PermissionUtil.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + pickContactEmail(); + } else { + requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); + } + } + + private void pickContactEmail() { + Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); + + if (intent.resolveActivity(requireContext().getPackageManager()) != null) { + onContactSelectionResultLauncher.launch(intent); + } else { + DisplayUtils.showSnackMessage(requireActivity(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message)); + } + } + + private void handleContactResult(@NonNull Uri contactUri) { + // Define the projection to get all email addresses. + String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; + + Cursor cursor = fileActivity.getContentResolver().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); + 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(() -> { + binding.searchView.setQuery(email, false); + binding.searchView.requestFocus(); + }); + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address."); + } + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); + } + cursor.close(); + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); + } + } + + private boolean containsNoNewPublicShare(List shares) { + for (OCShare share : shares) { + if (share.getShareType() == ShareType.NEW_PUBLIC_LINK) { + return false; + } + } + + return true; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + FileExtensionsKt.logFileSize(file, TAG); + outState.putParcelable(ARG_NOTE, file); + outState.putParcelable(ARG_USER, user); + } + + @Override + public void avatarGenerated(Drawable avatarDrawable, Object callContext) { + binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); + } + + @Override + public boolean shouldCallGeneratedCallback(String tag, Object callContext) { + return false; + } + + private boolean isReshareForbidden(OCShare share) { + return ShareType.FEDERATED == share.getShareType() || + capabilities != null && capabilities.getFilesSharingResharing().isFalse(); + } + + @VisibleForTesting + public void search(String query) { + SearchView searchView = requireView().findViewById(R.id.searchView); + searchView.setQuery(query, true); + } + + @Override + public void advancedPermissions(OCShare share) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); + } + + + @Override + public void sendNewEmail(OCShare share) { + modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); + } + + @Override + public void unShare(OCShare share) { + unshareWith(share); + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + if (adapter == null) { + DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui)); + return; + } + adapter.remove(share); + } + + @Override + public void sendLink(OCShare share) { + if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { + FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); + } else { + showSendLinkTo(share); + } + } + + @Override + public void addAnotherLink(OCShare share) { + createPublicShareLink(); + } + + private void modifyExistingShare(OCShare share, int screenTypePermission) { + onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), + capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); + } + + @Override + public void onQuickPermissionChanged(OCShare share, int permission) { + fileOperationsHelper.setPermissionsToShare(share, permission); + } + + //launcher for contact permission + private final ActivityResultLauncher requestContactPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + pickContactEmail(); + } else { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.contact_no_permission); + } + }); + + //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(binding.getRoot(), R.string.email_pick_failed); + return; + } + + Uri contactUri = intent.getData(); + if (contactUri == null) { + DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + return; + } + + handleContactResult(contactUri); + + } + }); + + public interface OnEditShareListener { + void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown, + boolean isExpiryDateShown); + + void onShareProcessClosed(); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java new file mode 100644 index 000000000..62eb8661a --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java @@ -0,0 +1,37 @@ +package it.niedermann.owncloud.notes.share; + + +import android.content.Context; +import android.os.Handler; + +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.common.User; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; + +import androidx.annotation.NonNull; + +public abstract class SyncOperation extends RemoteOperation { + private final User user; + + public SyncOperation(@NonNull User user) { + this.user = user; + } + + public RemoteOperationResult execute(Context context) { + return super.execute(user, context); + } + + public RemoteOperationResult execute(@NonNull NextcloudClient client) { + return run(client); + } + + public Thread execute(OwnCloudClient client, + OnRemoteOperationListener listener, + Handler listenerHandler) { + return super.execute(client, listener, listenerHandler); + } + +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt new file mode 100644 index 000000000..be773c7f7 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt @@ -0,0 +1,40 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2019 Chris Narkiewicz + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package it.niedermann.owncloud.notes.shared.server + +import android.os.Parcel +import android.os.Parcelable +import com.owncloud.android.lib.resources.status.OwnCloudVersion +import java.net.URI + +/** + * This object provides all information necessary to interact + * with backend server. + */ +data class Server(val uri: URI, val version: OwnCloudVersion) : Parcelable { + + constructor(source: Parcel) : this( + source.readSerializable() as URI, + source.readParcelable(OwnCloudVersion::class.java.classLoader) as OwnCloudVersion + ) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { + writeSerializable(uri) + writeParcelable(version, 0) + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): Server = Server(source) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt new file mode 100644 index 000000000..b68c4598d --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt @@ -0,0 +1,56 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2020 Chris Narkiewicz + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ +package it.niedermann.owncloud.notes.shared.user + +import android.accounts.Account +import android.os.Parcelable +import com.owncloud.android.lib.common.OwnCloudAccount +import it.niedermann.owncloud.notes.shared.server.Server + +interface User : Parcelable, com.nextcloud.common.User { + override val accountName: String + val server: Server + + /** + * This is temporary helper method created to facilitate incremental refactoring. + * Code using legacy platform Account can be partially converted to instantiate User + * object and use account instance when required. + * + * This method calls will allow tracing code awaiting further refactoring. + * + * @return Account instance that is associated with this User object. + */ + @Deprecated("Temporary workaround") + override fun toPlatformAccount(): Account + + /** + * This is temporary helper method created to facilitate incremental refactoring. + * Code using legacy ownCloud account can be partially converted to instantiate User + * object and use account instance when required. + * + * This method calls will allow tracing code awaiting further refactoring. + * + * @return OwnCloudAccount instance that is associated with this User object. + */ + @Deprecated("Temporary workaround") + fun toOwnCloudAccount(): OwnCloudAccount + + /** + * Compare account names, case insensitive. + * + * @return true if account names are same, false otherwise + */ + fun nameEquals(user: User?): Boolean + + /** + * Compare account names, case insensitive. + * + * @return true if account names are same, false otherwise + */ + fun nameEquals(accountName: CharSequence?): Boolean +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt new file mode 100644 index 000000000..cb6c5e14f --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt @@ -0,0 +1,50 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2018 Andy Scherzinger + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ + +package it.niedermann.owncloud.notes.shared.util + +import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.text.TextUtils +import android.widget.Toast +import com.owncloud.android.lib.common.utils.Log_OC +import it.niedermann.owncloud.notes.R + +/** + * Helper implementation to copy a string into the system clipboard. + */ +object ClipboardUtil { + private val TAG = ClipboardUtil::class.java.name + + @JvmStatic + @JvmOverloads + @Suppress("TooGenericExceptionCaught") + fun copyToClipboard(activity: Activity, text: String?, showToast: Boolean = true) { + if (!TextUtils.isEmpty(text)) { + try { + val clip = ClipData.newPlainText( + activity.getString( + R.string.clipboard_label, + activity.getString(R.string.app_name) + ), + text + ) + (activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(clip) + if (showToast) { + Toast.makeText(activity, R.string.clipboard_text_copied, Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(activity, R.string.clipboard_unexpected_error, Toast.LENGTH_SHORT).show() + Log_OC.e(TAG, "Exception caught while copying to clipboard", e) + } + } else { + Toast.makeText(activity, R.string.clipboard_no_text_to_copy, Toast.LENGTH_SHORT).show() + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bde9526c0..06af4e559 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,125 +38,19 @@ Choose a category - - com.nextcloud.android.providers.UsersAndGroupsSearchProvider - com.nextcloud.android.providers.UsersAndGroupsSearchProvider.action.SHARE_WITH - - - - @string/link_share_view_only - @string/link_share_allow_upload_and_editing - @string/link_share_file_drop - - - @string/link_share_view_only - @string/link_share_editing - - - - note activity icon - note share icon - note share external icon - note share copy icon - note share more icon - note share icon - note share user icon - note share contact icon - Shared with you by %1$s - Share note - Name, Federated Cloud ID or email address… - Share link - Policy or permissions prevent resharing - Could not retrieve URL - \"%1$s\" has been shared with you - Contact permission is required. - - - Advanced Settings - Hide download - Note to recipient - Note - Next - Share and Copy Link - Confirm - Set Note - Send Share - Name - Link Name - You must enter a password - Password - Please select at least one permission to share. - Label cannot be empty - Cancel - Failed to create a share - - - Enter an optional password - Skip - - Enter a password - OK - Delete - Send email - No app available to handle links - No App available to handle mail address - No actions for this user - - Failed to remove share - Failed to pick email address. - Failed to update UI - No app available to select contacts - Send link to… - - Send - Internal share link only works for users with access to this folder - Internal share link only works for users with access to this file - Share internal link - Delete Link - Settings - Send new email - Sharing - Share %1$s - Expires %1$s - %1$s - Set expiration date - Share link - Send link - Password-protected - Set password - Share with… - Unset - Add another link - Add new public share link - New name - Share link (%1$s) - Share link - Allow resharing - View only - Editing - Allow upload and editing - File drop (upload only) - Could not retrieve shares - View only - Can edit - File drop - Secure file drop - Share Permissions - - %1$d download remaining - %1$d downloads remaining - - Username - %1$s (group) - %1$s (remote) - %1$s (conversation) - on %1$s - (remote) - - + note share copy icon + note share more icon + note share icon + note share user icon + note share contact icon + Shared with you by %1$s + Name, Federated Cloud ID or email address… + Share link + Policy or permissions prevent resharing + Could not retrieve URL + \"%1$s\" has been shared with you Copy link - Share Link copied Received no text to copy to clipboard Unexpected error while copying to clipboard diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 5a692abf7..3b295593a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -9,7 +9,6 @@ - @@ -36,10 +35,7 @@ - - - - + @@ -111,7 +107,6 @@ - @@ -135,18 +130,15 @@ - - - @@ -1198,14 +1190,6 @@ - - - - - - - - @@ -1219,14 +1203,6 @@ - - - - - - - - @@ -1545,11 +1521,6 @@ - - - - - @@ -5069,14 +5040,6 @@ - - - - - - - - @@ -6426,14 +6389,6 @@ - - - - - - - - From 0d00294d164e17d41ddabd7e2e4094ac2de4cd57 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Tue, 28 Jan 2025 10:06:51 +0100 Subject: [PATCH 14/25] first successfull build Signed-off-by: alperozturk --- app/build.gradle | 9 +- .../notes/share/ClientFactoryImpl.java | 84 +++++++ .../notes/share/NoteShareFragment.java | 209 +++++++++++------- app/src/main/res/layout/activity_row.xml | 2 +- app/src/main/res/values/strings.xml | 75 +++++++ gradle/verification-metadata.xml | 30 ++- 6 files changed, 323 insertions(+), 86 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java diff --git a/app/build.gradle b/app/build.gradle index c18c1a1cb..513be6a83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,12 +95,17 @@ ext { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4' - implementation("com.github.nextcloud:android-library:3d422b28376339c0fbd772e480dbbdc56b7ae1a1") { + implementation 'com.google.guava:guava:31.1-android' + implementation ('commons-httpclient:commons-httpclient:3.1') { + exclude group: 'commons-logging', module: 'commons-logging' + } + + implementation("com.github.nextcloud:android-library:2.19.0") { exclude group: 'org.ogce', module: 'xpp3' } // Nextcloud SSO - implementation 'com.github.nextcloud.android-common:ui:0.24.0' + implementation 'com.github.nextcloud.android-common:ui:0.23.2' implementation 'com.github.nextcloud:Android-SingleSignOn:1.3.2' implementation 'com.github.stefan-niedermann:android-commons:1.0.2' implementation "com.github.stefan-niedermann.nextcloud-commons:sso-glide:$commonsVersion" diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java b/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java new file mode 100644 index 000000000..b9c82d25e --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java @@ -0,0 +1,84 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2019 Chris Narkiewicz + * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only + */ + +package it.niedermann.owncloud.notes.share.operations; + +import android.accounts.Account; +import android.accounts.AuthenticatorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import com.nextcloud.common.NextcloudClient; +import com.nextcloud.common.PlainClient; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; +import com.owncloud.android.lib.common.accounts.AccountUtils; + +import java.io.IOException; + +import it.niedermann.owncloud.notes.shared.user.User; + +public class ClientFactoryImpl implements ClientFactory { + + private Context context; + + public ClientFactoryImpl(Context context) { + this.context = context; + } + + @Override + public OwnCloudClient create(User user) throws CreationException { + try { + return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context); + } catch (OperationCanceledException | + AuthenticatorException | + IOException e) { + throw new CreationException(e); + } + } + + @Override + public NextcloudClient createNextcloudClient(User user) throws CreationException { + try { + return OwnCloudClientFactory.createNextcloudClient(user, context); + } catch (AccountUtils.AccountNotFoundException e) { + throw new CreationException(e); + } + } + + @Override + public OwnCloudClient create(Account account) + throws OperationCanceledException, AuthenticatorException, IOException, + AccountUtils.AccountNotFoundException { + return OwnCloudClientFactory.createOwnCloudClient(account, context); + } + + @Override + public OwnCloudClient create(Account account, Activity currentActivity) + throws OperationCanceledException, AuthenticatorException, IOException, + AccountUtils.AccountNotFoundException { + return OwnCloudClientFactory.createOwnCloudClient(account, context, currentActivity); + } + + @Override + public OwnCloudClient create(Uri uri, boolean followRedirects, boolean useNextcloudUserAgent) { + return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); + } + + @Override + public OwnCloudClient create(Uri uri, boolean followRedirects) { + return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); + } + + @Override + public PlainClient createPlainClient() { + return new PlainClient(context); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java index 5f1004d38..759c8a8f0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java @@ -3,8 +3,10 @@ import android.Manifest; import android.app.Activity; import android.app.SearchManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -15,6 +17,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -22,6 +25,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.widget.SearchView; +import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; @@ -29,6 +33,11 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; +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 java.util.ArrayList; import java.util.List; @@ -39,18 +48,26 @@ import it.niedermann.owncloud.notes.databinding.FragmentNoteShareBinding; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Note; +import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; +import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; +import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; +import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; +import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; +import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; +import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; +import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; import it.niedermann.owncloud.notes.shared.user.User; -import it.niedermann.owncloud.notes.shared.util.ClipboardUtil; import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; -public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, - DisplayUtils.AvatarGenerationListener, - Injectable, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { +public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { private static final String TAG = "NoteShareFragment"; private static final String ARG_NOTE = "NOTE"; private static final String ARG_ACCOUNT = "ACCOUNT"; private static final String ARG_USER = "USER"; + public static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; private FragmentNoteShareBinding binding; private Note note; @@ -58,6 +75,7 @@ public class NoteShareFragment extends Fragment implements ShareeListAdapterList private Account account; private OnEditShareListener onEditShareListener; + private ClientFactoryImpl clientFactory; public static NoteShareFragment newInstance(Note note, User user, Account account) { NoteShareFragment fragment = new NoteShareFragment(); @@ -73,6 +91,8 @@ public static NoteShareFragment newInstance(Note note, User user, Account accoun public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + clientFactory = new ClientFactoryImpl(requireContext()); + if (savedInstanceState != null) { note = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_NOTE, Note.class); account = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_ACCOUNT, Account.class); @@ -107,11 +127,11 @@ public void onActivityCreated(Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentNoteShareBinding.inflate(inflater, container, false); - binding.sharesList.setAdapter(new ShareeListAdapter(fileActivity, + binding.sharesList.setAdapter(new ShareeListAdapter(requireActivity(), new ArrayList<>(), this, user, - note)); + account)); binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); @@ -141,26 +161,29 @@ public void onAttach(@NonNull Context context) { @Override public void onStart() { super.onStart(); - searchConfig.setSearchOnlyUsers(file.isEncrypted()); + UsersAndGroupsSearchConfig.INSTANCE.setSearchOnlyUsers(true); } @Override public void onStop() { super.onStop(); - searchConfig.reset(); + UsersAndGroupsSearchConfig.INSTANCE.reset(); } private void setupView() { setShareWithYou(); - OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + + setupSearchView((SearchManager) requireContext().getSystemService(Context.SEARCH_SERVICE), binding.searchView, requireActivity().getComponentName()); + // viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); - FileDetailSharingFragmentHelper.setupSearchView( - (SearchManager) fileActivity.getSystemService(Context.SEARCH_SERVICE), - binding.searchView, - fileActivity.getComponentName()); - viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + /* if (file.canReshare()) { binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); } else { @@ -169,6 +192,39 @@ private void setupView() { binding.pickContactEmailBtn.setVisibility(View.GONE); disableSearchView(binding.searchView); } + */ + + } + + private void setupSearchView(@Nullable SearchManager searchManager, SearchView searchView, + ComponentName componentName) { + if (searchManager == null) { + searchView.setVisibility(View.GONE); + return; + } + + // assumes parent activity is the searchable activity + searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)); + + // do not iconify the widget; expand it by default + searchView.setIconifiedByDefault(false); + + // avoid fullscreen with softkeyboard + searchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + // return true to prevent the query from being processed; + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + // leave it for the parent listener in the hierarchy / default behaviour + return false; + } + }); } private void disableSearchView(View view) { @@ -223,7 +279,7 @@ private void setShareWithYou() { } } - private void copyInternalLink() { + public void copyInternalLink() { if (account == null) { BrandedSnackbar.make(requireView(), getString(R.string.note_share_fragment_could_not_retrieve_url), Snackbar.LENGTH_LONG) .setAnchorView(binding.sharesList) @@ -245,7 +301,7 @@ private void showShareLinkDialog() { String[] packagesToExclude = new String[] { requireContext().getPackageName() }; DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); - chooserDialog.show(getParentFragmentManager(), FileDisplayActivity.FTAG_CHOOSER_DIALOG); + chooserDialog.show(getParentFragmentManager(), FTAG_CHOOSER_DIALOG); } // TODO: Check account.getUrl returning base url? @@ -256,6 +312,7 @@ private String createInternalLink() { // TODO: Capabilities in notes app doesn't have following functions... public void createPublicShareLink() { + /* if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { // password enforced by server, request to the user before trying to create @@ -275,10 +332,12 @@ public void createPublicShareLink() { service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); } + */ + } - private void createSecureFileDrop() { - fileOperationsHelper.shareFolderViaSecureFileDrop(file); + public void createSecureFileDrop() { + // fileOperationsHelper.shareFolderViaSecureFileDrop(file); } /* @@ -303,6 +362,7 @@ public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewTheme } */ private void showSendLinkTo(OCShare publicShare) { + /* if (file.isSharedViaLink()) { if (TextUtils.isEmpty(publicShare.getShareLink())) { fileOperationsHelper.getFileWithLink(file, viewThemeUtils); @@ -311,9 +371,12 @@ private void showSendLinkTo(OCShare publicShare) { showShareLinkDialog(); } } + */ + } public void copyLink(OCShare share) { + /* if (file.isSharedViaLink()) { if (TextUtils.isEmpty(share.getShareLink())) { fileOperationsHelper.getFileWithLink(file, viewThemeUtils); @@ -321,51 +384,22 @@ public void copyLink(OCShare share) { ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); } } + */ + } - /** - * 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).show(); + if (getActivity() != null && !getActivity().isFinishing()) { + new FileDetailSharingMenuBottomSheetDialog(getActivity(), this, share).show(); } } - /** - * show quick sharing permission dialog - * - * @param share - */ @Override public void showPermissionsDialog(OCShare share) { - new QuickSharingPermissionsBottomSheetDialog(fileActivity, this, share, viewThemeUtils).show(); + new QuickSharingPermissionsBottomSheetDialog(getActivity(), this, share).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) { if (result.isSuccess()) { refreshUiFromDB(); @@ -384,7 +418,7 @@ private void refreshUiFromDB() { } private void unshareWith(OCShare share) { - fileOperationsHelper.unshareShare(file, share); + // fileOperationsHelper.unshareShare(file, share); } /** @@ -395,7 +429,7 @@ private void unshareWith(OCShare share) { * @param askForPassword if true, password is optional */ public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) { - SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(file, + SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(note, createShare, askForPassword); dialog.show(getChildFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); @@ -411,10 +445,10 @@ public void requestPasswordForShare(OCShare share, boolean askForPassword) { public void showProfileBottomSheet(User user, String shareWith) { if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { new RetrieveHoverCardAsyncTask(user, + account, shareWith, - fileActivity, - clientFactory, - viewThemeUtils).execute(); + getActivity(), + clientFactory).execute(); } } @@ -422,7 +456,7 @@ public void showProfileBottomSheet(User user, String shareWith) { * Get known server capabilities from DB */ public void refreshCapabilitiesFromDB() { - capabilities = fileDataStorageManager.getCapability(user.getAccountName()); + // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); } /** @@ -430,6 +464,7 @@ public void refreshCapabilitiesFromDB() { * before reading database. */ public void refreshSharesFromDB() { + /* OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); if (newFile != null) { file = newFile; @@ -438,7 +473,8 @@ public void refreshSharesFromDB() { ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { - DisplayUtils.showSnackMessage(getView(), getString(R.string.could_not_retrieve_shares)); + BrandedSnackbar.make(requireView(), getString(R.string.could_not_retrieve_shares), Snackbar.LENGTH_LONG) + .show(); return; } adapter.getShares().clear(); @@ -458,8 +494,7 @@ public void refreshSharesFromDB() { ShareType.PUBLIC_LINK, ""); - if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares()) && - (!file.isEncrypted() || capabilities.getEndToEndEncryption().isTrue())) { + if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) { final OCShare ocShare = new OCShare(); ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); publicShares.add(ocShare); @@ -468,10 +503,12 @@ public void refreshSharesFromDB() { } adapter.addShares(publicShares); + */ + } private void checkContactPermission() { - if (PermissionUtil.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { pickContactEmail(); } else { requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); @@ -484,7 +521,8 @@ private void pickContactEmail() { if (intent.resolveActivity(requireContext().getPackageManager()) != null) { onContactSelectionResultLauncher.launch(intent); } else { - DisplayUtils.showSnackMessage(requireActivity(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message)); + BrandedSnackbar.make(requireView(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message), Snackbar.LENGTH_LONG) + .show(); } } @@ -492,7 +530,7 @@ private void handleContactResult(@NonNull Uri contactUri) { // Define the projection to get all email addresses. String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; - Cursor cursor = fileActivity.getContentResolver().query(contactUri, projection, null, null, null); + Cursor cursor = requireActivity().getContentResolver().query(contactUri, projection, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { @@ -507,16 +545,19 @@ private void handleContactResult(@NonNull Uri contactUri) { binding.searchView.requestFocus(); }); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address."); } } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); } cursor.close(); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); } } @@ -534,24 +575,22 @@ private boolean containsNoNewPublicShare(List shares) { @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); - FileExtensionsKt.logFileSize(file, TAG); - outState.putParcelable(ARG_NOTE, file); + outState.putSerializable(ARG_NOTE, note); + outState.putSerializable(ARG_ACCOUNT, account); outState.putParcelable(ARG_USER, user); } - @Override public void avatarGenerated(Drawable avatarDrawable, Object callContext) { binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); } - @Override public boolean shouldCallGeneratedCallback(String tag, Object callContext) { return false; } private boolean isReshareForbidden(OCShare share) { - return ShareType.FEDERATED == share.getShareType() || - capabilities != null && capabilities.getFilesSharingResharing().isFalse(); + return false; + // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); } @VisibleForTesting @@ -562,13 +601,13 @@ public void search(String query) { @Override public void advancedPermissions(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); + // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); } @Override public void sendNewEmail(OCShare share) { - modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); + // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); } @Override @@ -576,7 +615,8 @@ public void unShare(OCShare share) { unshareWith(share); ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { - DisplayUtils.showSnackMessage(getView(), getString(R.string.failed_update_ui)); + BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); return; } adapter.remove(share); @@ -584,11 +624,14 @@ public void unShare(OCShare share) { @Override public void sendLink(OCShare share) { + /* if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); } else { showSendLinkTo(share); } + */ + } @Override @@ -597,13 +640,12 @@ public void addAnotherLink(OCShare share) { } private void modifyExistingShare(OCShare share, int screenTypePermission) { - onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), - capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); + // onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); } @Override public void onQuickPermissionChanged(OCShare share, int permission) { - fileOperationsHelper.setPermissionsToShare(share, permission); + // fileOperationsHelper.setPermissionsToShare(share, permission); } //launcher for contact permission @@ -612,7 +654,8 @@ public void onQuickPermissionChanged(OCShare share, int permission) { if (isGranted) { pickContactEmail(); } else { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.contact_no_permission); + BrandedSnackbar.make(binding.getRoot(), getString(R.string.contact_no_permission), Snackbar.LENGTH_LONG) + .show(); } }); @@ -623,13 +666,15 @@ public void onQuickPermissionChanged(OCShare share, int permission) { if (result.getResultCode() == Activity.RESULT_OK) { Intent intent = result.getData(); if (intent == null) { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); return; } Uri contactUri = intent.getData(); if (contactUri == null) { - DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed); + BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) + .show(); return; } diff --git a/app/src/main/res/layout/activity_row.xml b/app/src/main/res/layout/activity_row.xml index ffb8e2e6c..8d6e4d150 100644 --- a/app/src/main/res/layout/activity_row.xml +++ b/app/src/main/res/layout/activity_row.xml @@ -23,7 +23,7 @@ android:layout_height="@dimen/default_icon_size" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/spacer_2x" - android:contentDescription="@string/note_share_activity_activity_icon_content_description" /> + android:contentDescription="@string/note_share_fragment_activity_icon_content_description" /> Choose a category + + @string/link_share_view_only + @string/link_share_allow_upload_and_editing + @string/link_share_file_drop + + + @string/link_share_view_only + @string/link_share_editing + + note activity icon + note share icon + note share external icon note share copy icon note share more icon note share icon @@ -49,6 +61,69 @@ Policy or permissions prevent resharing Could not retrieve URL \"%1$s\" has been shared with you + Contact permission is required. + You must enter a password + Password + Enter an optional password + Skip + Cancel + + Enter a password + OK + Delete + Send email + No app available to handle links + No App available to handle mail address + No actions for this user + + Failed to pick email address. + Failed to update UI + No app available to select contacts + Send link to… + + Send + Internal share link only works for users with access to this folder + Internal share link only works for users with access to this file + Share internal link + Delete Link + Settings + Send new email + Sharing + Share %1$s + Expires %1$s + %1$s + Set expiration date + Share link + Send link + Password-protected + Set password + Share with… + Unset + Add another link + Add new public share link + New name + Share link (%1$s) + Share link + Allow resharing + View only + Editing + Allow upload and editing + File drop (upload only) + Could not retrieve shares + View only + Can edit + File drop + Secure file drop + Share Permissions + + %1$d download remaining + %1$d downloads remaining + + Username + %1$s (group) + %1$s (remote) + %1$s (conversation) + on %1$s Copy link Link copied diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 3b295593a..07f1c940a 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -35,7 +35,10 @@ - + + + + @@ -107,6 +110,7 @@ + @@ -130,15 +134,18 @@ + + + @@ -1521,6 +1528,11 @@ + + + + + @@ -5040,6 +5052,14 @@ + + + + + + + + @@ -6389,6 +6409,14 @@ + + + + + + + + From 5b96d8ae6162b4b7abc2c6256c555bb82ef69e43 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 3 Feb 2025 15:21:40 +0100 Subject: [PATCH 15/25] fix build Signed-off-by: alperozturk --- .../notes/persistence/dao/ShareDao.kt | 10 +- .../notes/share/NoteShareActivity.java | 384 ++++++++++++------ 2 files changed, 255 insertions(+), 139 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt index 15ab98f80..97f27cac0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt @@ -2,15 +2,15 @@ package it.niedermann.owncloud.notes.persistence.dao import androidx.room.Dao import androidx.room.Insert -import androidx.room.OnConflictStrategy import androidx.room.Query import it.niedermann.owncloud.notes.persistence.entity.ShareEntity @Dao interface ShareDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun addShareEntities(entities: List) - @Query("SELECT * FROM share_table WHERE path = :path") - fun getShareEntities(path: String): List + @Insert + fun addShareEntity(entity: ShareEntity) + + @Query("SELECT * FROM share_table WHERE noteRemoteId = :noteRemoteId AND userName = :userName") + fun getShareEntities(noteRemoteId: Long, userName: String): List } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 0401416eb..25408bd48 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -14,6 +14,7 @@ import android.text.InputType; import android.text.TextUtils; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; @@ -26,11 +27,17 @@ import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.LinearLayoutManager; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; +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 java.util.ArrayList; import java.util.List; @@ -38,7 +45,9 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.BrandedActivity; import it.niedermann.owncloud.notes.branding.BrandedSnackbar; @@ -46,24 +55,26 @@ import it.niedermann.owncloud.notes.databinding.ActivityNoteShareBinding; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Note; +import it.niedermann.owncloud.notes.persistence.entity.ShareEntity; import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; import it.niedermann.owncloud.notes.share.adapter.SuggestionAdapter; -import it.niedermann.owncloud.notes.share.dialog.NoteShareActivityShareItemActionBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; -import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; +import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; +import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; +import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; import it.niedermann.owncloud.notes.share.repository.ShareRepository; -import it.niedermann.owncloud.notes.shared.model.Capabilities; +import it.niedermann.owncloud.notes.shared.user.User; import it.niedermann.owncloud.notes.shared.util.DisplayUtils; import it.niedermann.owncloud.notes.shared.util.ShareUtil; -import it.niedermann.owncloud.notes.shared.util.clipboard.ClipboardUtil; import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; -public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, NoteShareItemAction, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { +public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { private static final String TAG = "NoteShareActivity"; public static final String ARG_NOTE = "NOTE"; @@ -77,8 +88,8 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap private ActivityNoteShareBinding binding; private Note note; private Account account; + private ClientFactoryImpl clientFactory; private ShareRepository repository; - private Capabilities capabilities; public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -99,12 +110,12 @@ private void initializeArguments() { throw new IllegalArgumentException("Account cannot be null"); } - executorService.schedule(() -> { + clientFactory = new ClientFactoryImpl(this); + + new Thread(() -> {{ try { final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); repository = new ShareRepository(NoteShareActivity.this, ssoAcc); - capabilities = repository.capabilities(); - repository.getSharesForNotesAndSaveShareEntities(); runOnUiThread(() -> { binding.sharesList.setAdapter(new ShareeListAdapter(this, new ArrayList<>(), this, account)); @@ -119,7 +130,7 @@ private void initializeArguments() { } catch (Exception e) { throw new RuntimeException(e); } - }, 0, TimeUnit.MICROSECONDS); + }}).start(); } @Override @@ -137,6 +148,23 @@ public void onStop() { private void setupView() { setShareWithYou(); setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); + + // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); + + // TODO: When to disable? + // binding.pickContactEmailBtn.setVisibility(View.GONE); + // disableSearchView(binding.searchView); + + /* + if (file.canReshare()) { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); + } else { + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + } + */ } private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) { @@ -145,7 +173,7 @@ private void setupSearchView(@Nullable SearchManager searchManager, ComponentNam return; } - SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null, account); + SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null); UsersAndGroupsSearchProvider provider = new UsersAndGroupsSearchProvider(this, repository); binding.searchView.setSuggestionsAdapter(suggestionAdapter); @@ -179,16 +207,11 @@ public boolean onQueryTextChange(String newText) { // Schedule a new task with a delay future = executorService.schedule(() -> { - if (capabilities == null) { - Log_OC.d(TAG, "Capabilities cannot be null"); - return; - } - - final var isFederationShareAllowed = capabilities.getFederationShare(); - try(var cursor = provider.searchForUsersOrGroups(newText, isFederationShareAllowed)) { - runOnUiThread(() -> { - { + try { + provider.searchForUsersOrGroups(newText, cursor -> { + runOnUiThread(() -> {{ if (cursor == null || cursor.getCount() == 0) { + suggestionAdapter.changeCursor(null); return; } @@ -197,7 +220,7 @@ public boolean onQueryTextChange(String newText) { } binding.progressBar.setVisibility(View.GONE); - } + }}); }); } catch (Exception e) { Log_OC.d(TAG, "Exception setupSearchView.onQueryTextChange: " + e); @@ -243,60 +266,74 @@ private void navigateNoteShareDetail(String shareWith, int shareType) { startActivity(intent); } + private void disableSearchView(View view) { + view.setEnabled(false); + + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + disableSearchView(viewGroup.getChildAt(i)); + } + } + } + + // TODO: Check if note.getAccountId() return note's owner's id private boolean accountOwnsFile() { - String displayName = account.getDisplayName(); - return TextUtils.isEmpty(displayName) || account.getAccountName().split("@")[0].equalsIgnoreCase(displayName); + String noteId = String.valueOf(note.getAccountId()); + return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); } private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); - } - } + } else { - public void copyInternalLink() { - if (account == null) { - DisplayUtils.showSnackMessage(this, getString(R.string.note_share_activity_could_not_retrieve_url)); - return; - } + /* + // TODO: How to get owner display name from note? - final var link = createInternalLink(); - showShareLinkDialog(link); - } + binding.sharedWithYouUsername.setText( + String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); + */ - @Override - public void createPublicShareLink() { - if (capabilities == null) { - Log_OC.d(TAG, "Capabilities cannot be null"); - return; - } - if (capabilities.getPublicPasswordEnforced() || capabilities.getAskForOptionalPassword()) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, capabilities.getAskForOptionalPassword()); - } else { - executorService.schedule(() -> { - final var result = repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); - if (result != null) { - note.setIsShared(true); - repository.updateNote(note); - runOnUiThread(this::recreate); - } - }, 0, TimeUnit.MICROSECONDS); + loadAvatar(); + + /* + // TODO: Note's note? + String note = file.getNote(); + + if (!TextUtils.isEmpty(note)) { + binding.sharedWithYouNote.setText(file.getNote()); + binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); + } else { + binding.sharedWithYouNoteContainer.setVisibility(View.GONE); + } + */ } } - public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) { - SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(note, createShare, askForPassword); - dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + private void loadAvatar() { + Glide.with(this) + .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) + .placeholder(R.drawable.ic_account_circle_grey_24dp) + .error(R.drawable.ic_account_circle_grey_24dp) + .apply(RequestOptions.circleCropTransform()) + .into(binding.sharedWithYouAvatar); + + binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); } - @Override - public void createSecureFileDrop() { + public void copyInternalLink() { + if (account == null) { + DisplayUtils.showSnackMessage(this, getString(R.string.note_share_activity_could_not_retrieve_url)); + return; + } + showShareLinkDialog(); } - private void showShareLinkDialog(String link) { + private void showShareLinkDialog() { + String link = createInternalLink(); + Intent intentToShareLink = new Intent(Intent.ACTION_SEND); intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); @@ -313,31 +350,85 @@ private String createInternalLink() { return baseUri + "/index.php/f/" + note.getRemoteId(); } - @Override - public void copyLink(OCShare share) { - if (!note.isShared()) { - return; + // TODO: Capabilities in notes app doesn't have following functions... + public void createPublicShareLink() { + /* + if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, + capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); + + } else { + // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note + // Is CreateShareViaLink operation compatible? + + Intent service = new Intent(fileActivity, OperationsService.class); + service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); + service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); + if (!TextUtils.isEmpty(password)) { + service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); + } + service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); } + */ + } + + public void createSecureFileDrop() { + // fileOperationsHelper.shareFolderViaSecureFileDrop(file); + } - if (TextUtils.isEmpty(share.getShareLink())) { - copyAndShareFileLink(share.getShareLink()); + /* + // TODO: Cant call getFileWithLink + + public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { + List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), + ShareType.PUBLIC_LINK, + ""); + + if (shares.size() == SINGLE_LINK_SIZE) { + FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); } else { - ClipboardUtil.copyToClipboard(this, share.getShareLink()); + if (fileActivity instanceof FileDisplayActivity) { + ((FileDisplayActivity) fileActivity).showDetails(file, 1); + } else { + showShareFile(file); + } } + + fileActivity.refreshList(); + } + */ + private void showSendLinkTo(OCShare publicShare) { + /* + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(publicShare.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + // TODO: get link from public share and pass to the function + showShareLinkDialog(); + } + } + */ } - private void copyAndShareFileLink(String link) { - ClipboardUtil.copyToClipboard(this, link, false); - Snackbar snackbar = Snackbar - .make(this.findViewById(android.R.id.content), R.string.clipboard_text_copied, Snackbar.LENGTH_LONG) - .setAction(R.string.share, v -> showShareLinkDialog(link)); - snackbar.show(); + public void copyLink(OCShare share) { + /* + if (file.isSharedViaLink()) { + if (TextUtils.isEmpty(share.getShareLink())) { + fileOperationsHelper.getFileWithLink(file, viewThemeUtils); + } else { + ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + } + } + */ } @Override public void showSharingMenuActionSheet(OCShare share) { if (!this.isFinishing()) { - new NoteShareActivityShareItemActionBottomSheetDialog(this, this, share).show(); + new FileDetailSharingMenuBottomSheetDialog(this, this, share).show(); } } @@ -346,6 +437,41 @@ public void showPermissionsDialog(OCShare share) { new QuickSharingPermissionsBottomSheetDialog(this, this, share).show(); } + public void onUpdateShareInformation(RemoteOperationResult result) { + if (result.isSuccess()) { + refreshUiFromDB(); + } else { + setupView(); + } + } + + /** + * Get {@link OCShare} instance from DB and updates the UI. + */ + private void refreshUiFromDB() { + refreshSharesFromDB(); + // Updates UI with new state + setupView(); + } + + private void unshareWith(OCShare share) { + repository.removeShare(share.getId()); + } + + /** + * 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(note, + createShare, + askForPassword); + dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + } + @Override public void requestPasswordForShare(OCShare share, boolean askForPassword) { SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(share, askForPassword); @@ -353,15 +479,31 @@ public void requestPasswordForShare(OCShare share, boolean askForPassword) { } @Override - public void showProfileBottomSheet(Account account, String shareWith) { + public void showProfileBottomSheet(User user, String shareWith) { + if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { + new RetrieveHoverCardAsyncTask(user, + account, + shareWith, + this, + clientFactory).execute(); + } } + /** + * Get known server capabilities from DB + */ public void refreshCapabilitiesFromDB() { + // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); } + /** + * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities + * before reading database. + */ public void refreshSharesFromDB() { - executorService.schedule(() -> { + new Thread(() -> { try { + final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { @@ -374,8 +516,8 @@ public void refreshSharesFromDB() { // to show share with users/groups info List shares = new ArrayList<>(); - if (note != null) { - final var shareEntities = repository.getShareEntitiesForSpecificNote(note); + if (note != null && note.getRemoteId() != null) { + final var shareEntities = repository.getShareEntities(note.getRemoteId(), ssoAcc.name); shareEntities.forEach(entity -> { if (entity.getId() != null) { final var share = repository.getShares(entity.getId()); @@ -388,26 +530,25 @@ public void refreshSharesFromDB() { runOnUiThread(() -> { adapter.addShares(shares); - addPublicShares(adapter); + + // TODO: Will be added later on... + List publicShares = new ArrayList<>(); + + if (containsNoNewPublicShare(adapter.getShares())) { + final OCShare ocShare = new OCShare(); + ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); + publicShares.add(ocShare); + } else { + adapter.removeNewPublicShare(); + } + + adapter.addShares(publicShares); }); } catch (Exception e) { - Log_OC.d(TAG, "Exception while refreshSharesFromDB: " + e); + throw new RuntimeException(e); } - }, 0, TimeUnit.MICROSECONDS); - } - - private void addPublicShares(ShareeListAdapter adapter) { - List publicShares = new ArrayList<>(); - - if (containsNoNewPublicShare(adapter.getShares())) { - final OCShare ocShare = new OCShare(); - ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); - publicShares.add(ocShare); - } else { - adapter.removeNewPublicShare(); - } + }).start(); - adapter.addShares(publicShares); } private void checkContactPermission() { @@ -480,6 +621,7 @@ public void onSaveInstanceState(@NonNull Bundle outState) { private boolean isReshareForbidden(OCShare share) { return false; + // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); } @VisibleForTesting @@ -501,26 +643,24 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { - executorService.schedule(() -> { - final var result = repository.removeShare(share, note); - - runOnUiThread(() -> { - if (result) { - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - if (adapter == null) { - DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.email_pick_failed)); - return; - } - adapter.remove(share); - } else { - DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.failed_the_remove_share)); - } - }); - }, 0, TimeUnit.MICROSECONDS); + unshareWith(share); + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + if (adapter == null) { + DisplayUtils.showSnackMessage(this, getString(R.string.email_pick_failed)); + return; + } + adapter.remove(share); } @Override public void sendLink(OCShare share) { + /* + if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { + FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); + } else { + showSendLinkTo(share); + } + */ } @Override @@ -534,38 +674,19 @@ private void modifyExistingShare(OCShare share, int screenTypePermission) { bundle.putSerializable(NoteShareDetailActivity.ARG_OCSHARE, share); bundle.putInt(NoteShareDetailActivity.ARG_SCREEN_TYPE, screenTypePermission); bundle.putBoolean(NoteShareDetailActivity.ARG_RESHARE_SHOWN, !isReshareForbidden(share)); - bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, getExpDateShown()); + bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, true); // TODO: capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18) Intent intent = new Intent(this, NoteShareDetailActivity.class); intent.putExtras(bundle); startActivity(intent); } - private boolean getExpDateShown() { - try { - if (capabilities == null) { - Log_OC.d(TAG, "Capabilities cannot be null"); - return false; - } - - final var majorVersionAsString = capabilities.getNextcloudMajorVersion(); - if (majorVersionAsString != null) { - final var majorVersion = Integer.parseInt(majorVersionAsString); - return majorVersion >= 18; - } - - return false; - } catch (NumberFormatException e) { - Log_OC.d(TAG, "Exception while getting expDateShown"); - return false; - } - } - @Override public void onQuickPermissionChanged(OCShare share, int permission) { repository.updateSharePermission(share.getId(), permission); } + //launcher for contact permission private final ActivityResultLauncher requestContactPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { @@ -576,6 +697,7 @@ public void onQuickPermissionChanged(OCShare share, int permission) { } }); + //launcher to handle contact selection private final ActivityResultLauncher onContactSelectionResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { @@ -606,10 +728,4 @@ public void applyBrand(int color) { util.androidx.themeToolbarSearchView(binding.searchView); util.platform.themeHorizontalProgressBar(binding.progressBar); } - - @Override - protected void onDestroy() { - executorService.shutdown(); - super.onDestroy(); - } } From 7446b350206b0bba9287a02ac8b9656d003267ff Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:07:33 +0100 Subject: [PATCH 16/25] add createPublicShareLink Signed-off-by: alperozturk --- .../notes/share/NoteShareActivity.java | 364 ++++++------------ 1 file changed, 120 insertions(+), 244 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 25408bd48..4295b1896 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -14,7 +14,6 @@ import android.text.InputType; import android.text.TextUtils; import android.view.View; -import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; @@ -27,17 +26,12 @@ import androidx.fragment.app.DialogFragment; import androidx.recyclerview.widget.LinearLayoutManager; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; import com.google.android.material.snackbar.Snackbar; -import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; -import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; 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 java.util.ArrayList; import java.util.List; @@ -45,9 +39,7 @@ import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.BrandedActivity; import it.niedermann.owncloud.notes.branding.BrandedSnackbar; @@ -55,26 +47,25 @@ import it.niedermann.owncloud.notes.databinding.ActivityNoteShareBinding; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Note; -import it.niedermann.owncloud.notes.persistence.entity.ShareEntity; import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; import it.niedermann.owncloud.notes.share.adapter.SuggestionAdapter; -import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; +import it.niedermann.owncloud.notes.share.dialog.NoteShareActivityShareItemActionBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; -import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; +import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; +import it.niedermann.owncloud.notes.share.model.CreateShareRequest; import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; -import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; -import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; import it.niedermann.owncloud.notes.share.repository.ShareRepository; -import it.niedermann.owncloud.notes.shared.user.User; +import it.niedermann.owncloud.notes.shared.model.Capabilities; import it.niedermann.owncloud.notes.shared.util.DisplayUtils; import it.niedermann.owncloud.notes.shared.util.ShareUtil; +import it.niedermann.owncloud.notes.shared.util.clipboard.ClipboardUtil; import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; -public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { +public class NoteShareActivity extends BrandedActivity implements ShareeListAdapterListener, NoteShareItemAction, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { private static final String TAG = "NoteShareActivity"; public static final String ARG_NOTE = "NOTE"; @@ -88,8 +79,8 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap private ActivityNoteShareBinding binding; private Note note; private Account account; - private ClientFactoryImpl clientFactory; private ShareRepository repository; + private Capabilities capabilities; public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -110,12 +101,12 @@ private void initializeArguments() { throw new IllegalArgumentException("Account cannot be null"); } - clientFactory = new ClientFactoryImpl(this); - - new Thread(() -> {{ + executorService.schedule(() -> { try { final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); repository = new ShareRepository(NoteShareActivity.this, ssoAcc); + capabilities = repository.capabilities(); + repository.getSharesForNotesAndSaveShareEntities(); runOnUiThread(() -> { binding.sharesList.setAdapter(new ShareeListAdapter(this, new ArrayList<>(), this, account)); @@ -130,7 +121,7 @@ private void initializeArguments() { } catch (Exception e) { throw new RuntimeException(e); } - }}).start(); + }, 0, TimeUnit.MICROSECONDS); } @Override @@ -148,23 +139,6 @@ public void onStop() { private void setupView() { setShareWithYou(); setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); - - // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); - - // TODO: When to disable? - // binding.pickContactEmailBtn.setVisibility(View.GONE); - // disableSearchView(binding.searchView); - - /* - if (file.canReshare()) { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); - } else { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - } - */ } private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) { @@ -173,7 +147,7 @@ private void setupSearchView(@Nullable SearchManager searchManager, ComponentNam return; } - SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null); + SuggestionAdapter suggestionAdapter = new SuggestionAdapter(this, null, account); UsersAndGroupsSearchProvider provider = new UsersAndGroupsSearchProvider(this, repository); binding.searchView.setSuggestionsAdapter(suggestionAdapter); @@ -207,11 +181,16 @@ public boolean onQueryTextChange(String newText) { // Schedule a new task with a delay future = executorService.schedule(() -> { - try { - provider.searchForUsersOrGroups(newText, cursor -> { - runOnUiThread(() -> {{ + if (capabilities == null) { + Log_OC.d(TAG, "Capabilities cannot be null"); + return; + } + + final var isFederationShareAllowed = capabilities.getFederationShare(); + try(var cursor = provider.searchForUsersOrGroups(newText, isFederationShareAllowed)) { + runOnUiThread(() -> { + { if (cursor == null || cursor.getCount() == 0) { - suggestionAdapter.changeCursor(null); return; } @@ -220,7 +199,7 @@ public boolean onQueryTextChange(String newText) { } binding.progressBar.setVisibility(View.GONE); - }}); + } }); } catch (Exception e) { Log_OC.d(TAG, "Exception setupSearchView.onQueryTextChange: " + e); @@ -266,62 +245,17 @@ private void navigateNoteShareDetail(String shareWith, int shareType) { startActivity(intent); } - private void disableSearchView(View view) { - view.setEnabled(false); - - if (view instanceof ViewGroup viewGroup) { - for (int i = 0; i < viewGroup.getChildCount(); i++) { - disableSearchView(viewGroup.getChildAt(i)); - } - } - } - - // TODO: Check if note.getAccountId() return note's owner's id private boolean accountOwnsFile() { - String noteId = String.valueOf(note.getAccountId()); - return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); + String displayName = account.getDisplayName(); + return TextUtils.isEmpty(displayName) || account.getAccountName().split("@")[0].equalsIgnoreCase(displayName); } private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); - } else { - - /* - // TODO: How to get owner display name from note? - - binding.sharedWithYouUsername.setText( - String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); - */ - - - loadAvatar(); - - /* - // TODO: Note's note? - String note = file.getNote(); - - if (!TextUtils.isEmpty(note)) { - binding.sharedWithYouNote.setText(file.getNote()); - binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); - } else { - binding.sharedWithYouNoteContainer.setVisibility(View.GONE); - } - */ } } - private void loadAvatar() { - Glide.with(this) - .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) - .placeholder(R.drawable.ic_account_circle_grey_24dp) - .error(R.drawable.ic_account_circle_grey_24dp) - .apply(RequestOptions.circleCropTransform()) - .into(binding.sharedWithYouAvatar); - - binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); - } - public void copyInternalLink() { if (account == null) { DisplayUtils.showSnackMessage(this, getString(R.string.note_share_activity_could_not_retrieve_url)); @@ -331,6 +265,33 @@ public void copyInternalLink() { showShareLinkDialog(); } + @Override + public void createPublicShareLink() { + if (capabilities == null) { + Log_OC.d(TAG, "Capabilities cannot be null"); + return; + } + + if (capabilities.getPublicPasswordEnforced() || capabilities.getAskForOptionalPassword()) { + // password enforced by server, request to the user before trying to create + requestPasswordForShareViaLink(true, capabilities.getAskForOptionalPassword()); + } else { + executorService.schedule(() -> { + repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); + }, 0, TimeUnit.MICROSECONDS); + } + } + + public void requestPasswordForShareViaLink(boolean createShare, boolean askForPassword) { + SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(note, createShare, askForPassword); + dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); + } + + @Override + public void createSecureFileDrop() { + + } + private void showShareLinkDialog() { String link = createInternalLink(); @@ -350,85 +311,25 @@ private String createInternalLink() { return baseUri + "/index.php/f/" + note.getRemoteId(); } - // TODO: Capabilities in notes app doesn't have following functions... - public void createPublicShareLink() { - /* - if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - - } else { - // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note - // Is CreateShareViaLink operation compatible? - - Intent service = new Intent(fileActivity, OperationsService.class); - service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); - service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); - if (!TextUtils.isEmpty(password)) { - service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); - } - service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); - mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); - } - */ - } - - public void createSecureFileDrop() { - // fileOperationsHelper.shareFolderViaSecureFileDrop(file); - } - - /* - // TODO: Cant call getFileWithLink - - public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { - List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - if (shares.size() == SINGLE_LINK_SIZE) { - FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); - } else { - if (fileActivity instanceof FileDisplayActivity) { - ((FileDisplayActivity) fileActivity).showDetails(file, 1); - } else { - showShareFile(file); - } - } - - fileActivity.refreshList(); - } - */ - private void showSendLinkTo(OCShare publicShare) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(publicShare.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - // TODO: get link from public share and pass to the function - showShareLinkDialog(); - } - } - */ - } - + @Override public void copyLink(OCShare share) { /* if (file.isSharedViaLink()) { if (TextUtils.isEmpty(share.getShareLink())) { fileOperationsHelper.getFileWithLink(file, viewThemeUtils); } else { - ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); + ClipboardUtil.copyToClipboard(this, share.getShareLink()); } } */ + + ClipboardUtil.copyToClipboard(this, share.getShareLink()); } @Override public void showSharingMenuActionSheet(OCShare share) { if (!this.isFinishing()) { - new FileDetailSharingMenuBottomSheetDialog(this, this, share).show(); + new NoteShareActivityShareItemActionBottomSheetDialog(this, this, share).show(); } } @@ -437,41 +338,6 @@ public void showPermissionsDialog(OCShare share) { new QuickSharingPermissionsBottomSheetDialog(this, this, share).show(); } - public void onUpdateShareInformation(RemoteOperationResult result) { - if (result.isSuccess()) { - refreshUiFromDB(); - } else { - setupView(); - } - } - - /** - * Get {@link OCShare} instance from DB and updates the UI. - */ - private void refreshUiFromDB() { - refreshSharesFromDB(); - // Updates UI with new state - setupView(); - } - - private void unshareWith(OCShare share) { - repository.removeShare(share.getId()); - } - - /** - * 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(note, - createShare, - askForPassword); - dialog.show(getSupportFragmentManager(), SharePasswordDialogFragment.PASSWORD_FRAGMENT); - } - @Override public void requestPasswordForShare(OCShare share, boolean askForPassword) { SharePasswordDialogFragment dialog = SharePasswordDialogFragment.newInstance(share, askForPassword); @@ -479,31 +345,15 @@ public void requestPasswordForShare(OCShare share, boolean askForPassword) { } @Override - public void showProfileBottomSheet(User user, String shareWith) { - if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - new RetrieveHoverCardAsyncTask(user, - account, - shareWith, - this, - clientFactory).execute(); - } + public void showProfileBottomSheet(Account account, String shareWith) { } - /** - * Get known server capabilities from DB - */ public void refreshCapabilitiesFromDB() { - // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); } - /** - * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities - * before reading database. - */ public void refreshSharesFromDB() { - new Thread(() -> { + executorService.schedule(() -> { try { - final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); if (adapter == null) { @@ -516,8 +366,8 @@ public void refreshSharesFromDB() { // to show share with users/groups info List shares = new ArrayList<>(); - if (note != null && note.getRemoteId() != null) { - final var shareEntities = repository.getShareEntities(note.getRemoteId(), ssoAcc.name); + if (note != null) { + final var shareEntities = repository.getShareEntitiesForSpecificNote(note); shareEntities.forEach(entity -> { if (entity.getId() != null) { final var share = repository.getShares(entity.getId()); @@ -530,25 +380,26 @@ public void refreshSharesFromDB() { runOnUiThread(() -> { adapter.addShares(shares); - - // TODO: Will be added later on... - List publicShares = new ArrayList<>(); - - if (containsNoNewPublicShare(adapter.getShares())) { - final OCShare ocShare = new OCShare(); - ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); - publicShares.add(ocShare); - } else { - adapter.removeNewPublicShare(); - } - - adapter.addShares(publicShares); + addPublicShares(adapter); }); } catch (Exception e) { - throw new RuntimeException(e); + Log_OC.d(TAG, "Exception while refreshSharesFromDB: " + e); } - }).start(); + }, 0, TimeUnit.MICROSECONDS); + } + + private void addPublicShares(ShareeListAdapter adapter) { + List publicShares = new ArrayList<>(); + + if (containsNoNewPublicShare(adapter.getShares())) { + final OCShare ocShare = new OCShare(); + ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); + publicShares.add(ocShare); + } else { + adapter.removeNewPublicShare(); + } + adapter.addShares(publicShares); } private void checkContactPermission() { @@ -621,7 +472,6 @@ public void onSaveInstanceState(@NonNull Bundle outState) { private boolean isReshareForbidden(OCShare share) { return false; - // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); } @VisibleForTesting @@ -643,24 +493,26 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { - unshareWith(share); - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - if (adapter == null) { - DisplayUtils.showSnackMessage(this, getString(R.string.email_pick_failed)); - return; - } - adapter.remove(share); + executorService.schedule(() -> { + final var result = repository.removeShare(share.getId()); + + runOnUiThread(() -> { + if (result) { + ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); + if (adapter == null) { + DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.email_pick_failed)); + return; + } + adapter.remove(share); + } else { + DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.failed_the_remove_share)); + } + }); + }, 0, TimeUnit.MICROSECONDS); } @Override public void sendLink(OCShare share) { - /* - if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { - FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); - } else { - showSendLinkTo(share); - } - */ } @Override @@ -674,19 +526,38 @@ private void modifyExistingShare(OCShare share, int screenTypePermission) { bundle.putSerializable(NoteShareDetailActivity.ARG_OCSHARE, share); bundle.putInt(NoteShareDetailActivity.ARG_SCREEN_TYPE, screenTypePermission); bundle.putBoolean(NoteShareDetailActivity.ARG_RESHARE_SHOWN, !isReshareForbidden(share)); - bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, true); // TODO: capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18) + bundle.putBoolean(NoteShareDetailActivity.ARG_EXP_DATE_SHOWN, getExpDateShown()); Intent intent = new Intent(this, NoteShareDetailActivity.class); intent.putExtras(bundle); startActivity(intent); } + private boolean getExpDateShown() { + try { + if (capabilities == null) { + Log_OC.d(TAG, "Capabilities cannot be null"); + return false; + } + + final var majorVersionAsString = capabilities.getNextcloudMajorVersion(); + if (majorVersionAsString != null) { + final var majorVersion = Integer.parseInt(majorVersionAsString); + return majorVersion >= 18; + } + + return false; + } catch (NumberFormatException e) { + Log_OC.d(TAG, "Exception while getting expDateShown"); + return false; + } + } + @Override public void onQuickPermissionChanged(OCShare share, int permission) { repository.updateSharePermission(share.getId(), permission); } - //launcher for contact permission private final ActivityResultLauncher requestContactPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { @@ -697,7 +568,6 @@ public void onQuickPermissionChanged(OCShare share, int permission) { } }); - //launcher to handle contact selection private final ActivityResultLauncher onContactSelectionResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { @@ -728,4 +598,10 @@ public void applyBrand(int color) { util.androidx.themeToolbarSearchView(binding.searchView); util.platform.themeHorizontalProgressBar(binding.progressBar); } + + @Override + protected void onDestroy() { + executorService.shutdown(); + super.onDestroy(); + } } From c82dedde89092d334208cf6edd4ccc5ee8a951ed Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 5 Feb 2025 16:18:39 +0100 Subject: [PATCH 17/25] add copyAndShareFileLink Signed-off-by: alperozturk --- .../owncloud/notes/share/NoteShareActivity.java | 14 +++++++++++++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 4295b1896..77cc5d39e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -323,7 +323,19 @@ public void copyLink(OCShare share) { } */ - ClipboardUtil.copyToClipboard(this, share.getShareLink()); + if (TextUtils.isEmpty(share.getShareLink())) { + copyAndShareFileLink(share.getShareLink()); + } else { + ClipboardUtil.copyToClipboard(this, share.getShareLink()); + } + } + + private void copyAndShareFileLink(String link) { + ClipboardUtil.copyToClipboard(this, link, false); + Snackbar snackbar = Snackbar + .make(this.findViewById(android.R.id.content), R.string.clipboard_text_copied, Snackbar.LENGTH_LONG) + .setAction(R.string.share, v -> showShareLinkDialog()); + snackbar.show(); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 944bfa5c3..1d238f50a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -126,6 +126,7 @@ on %1$s Copy link + Share Link copied Received no text to copy to clipboard Unexpected error while copying to clipboard From 5fb241954f5bfe74e2aeb09cc8244ff176096f81 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 10 Feb 2025 09:42:18 +0100 Subject: [PATCH 18/25] add setShareWithYou Signed-off-by: alperozturk --- .../owncloud/notes/share/NoteShareActivity.java | 17 +++++++++++++++++ app/src/main/res/layout/activity_note_share.xml | 1 - 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 77cc5d39e..9c96341a8 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -53,6 +53,7 @@ import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; +import it.niedermann.owncloud.notes.share.helper.AvatarLoader; import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; @@ -253,6 +254,22 @@ private boolean accountOwnsFile() { private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); + } else { + // TODO: ADD + binding.sharedWithYouUsername.setText( + String.format(getString(R.string.note_share_activity_shared_with_you), "file.getOwnerDisplayName()")); + AvatarLoader.INSTANCE.load(this,binding.sharedWithYouAvatar,account); + binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); + + // TODO: ADD + String note = "file.getNote()"; + + if (!TextUtils.isEmpty(note)) { + binding.sharedWithYouNote.setText(note); + binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); + } else { + binding.sharedWithYouNoteContainer.setVisibility(View.GONE); + } } } diff --git a/app/src/main/res/layout/activity_note_share.xml b/app/src/main/res/layout/activity_note_share.xml index b4667a60b..7310f6938 100644 --- a/app/src/main/res/layout/activity_note_share.xml +++ b/app/src/main/res/layout/activity_note_share.xml @@ -80,7 +80,6 @@ Date: Mon, 10 Feb 2025 14:22:26 +0100 Subject: [PATCH 19/25] fix else logic Signed-off-by: alperozturk --- .../notes/share/NoteShareActivity.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 9c96341a8..183f73c8b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -82,6 +82,7 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap private Account account; private ShareRepository repository; private Capabilities capabilities; + private final List shares = new ArrayList<>(); public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -115,7 +116,7 @@ private void initializeArguments() { binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); binding.btnShareButton.setOnClickListener(v -> ShareUtil.openShareDialog(this, note.getTitle(), note.getContent())); - setupView(); + setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); refreshCapabilitiesFromDB(); refreshSharesFromDB(); }); @@ -137,11 +138,6 @@ public void onStop() { UsersAndGroupsSearchConfig.INSTANCE.reset(); } - private void setupView() { - setShareWithYou(); - setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); - } - private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) { if (searchManager == null) { binding.searchView.setVisibility(View.GONE); @@ -255,17 +251,22 @@ private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); } else { - // TODO: ADD + final var remoteId = note.getRemoteId(); + if (remoteId == null) { + return; + } + + final var share = shares.get(remoteId.intValue()); + binding.sharedWithYouUsername.setText( - String.format(getString(R.string.note_share_activity_shared_with_you), "file.getOwnerDisplayName()")); - AvatarLoader.INSTANCE.load(this,binding.sharedWithYouAvatar,account); + String.format(getString(R.string.note_share_activity_shared_with_you), share.getOwnerDisplayName())); + AvatarLoader.INSTANCE.load(this, binding.sharedWithYouAvatar, account); binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); - // TODO: ADD - String note = "file.getNote()"; + String description = share.getNote(); - if (!TextUtils.isEmpty(note)) { - binding.sharedWithYouNote.setText(note); + if (!TextUtils.isEmpty(description)) { + binding.sharedWithYouNote.setText(description); binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); } else { binding.sharedWithYouNoteContainer.setVisibility(View.GONE); @@ -393,8 +394,6 @@ public void refreshSharesFromDB() { adapter.getShares().clear(); // to show share with users/groups info - List shares = new ArrayList<>(); - if (note != null) { final var shareEntities = repository.getShareEntitiesForSpecificNote(note); shareEntities.forEach(entity -> { @@ -410,6 +409,7 @@ public void refreshSharesFromDB() { runOnUiThread(() -> { adapter.addShares(shares); addPublicShares(adapter); + setShareWithYou(); }); } catch (Exception e) { Log_OC.d(TAG, "Exception while refreshSharesFromDB: " + e); From 88bf3fd69e6cb9ee719774fa4a4194c73719f713 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 10 Feb 2025 14:24:01 +0100 Subject: [PATCH 20/25] remove unnecessary delay logic Signed-off-by: alperozturk --- .../notes/share/NoteShareActivity.java | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 183f73c8b..deec150c0 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -29,7 +29,6 @@ import com.google.android.material.snackbar.Snackbar; import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; import com.owncloud.android.lib.resources.shares.OCShare; import com.owncloud.android.lib.resources.shares.ShareType; @@ -57,7 +56,6 @@ import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider; import it.niedermann.owncloud.notes.share.listener.NoteShareItemAction; import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; -import it.niedermann.owncloud.notes.share.model.CreateShareRequest; import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; import it.niedermann.owncloud.notes.share.repository.ShareRepository; import it.niedermann.owncloud.notes.shared.model.Capabilities; @@ -103,7 +101,7 @@ private void initializeArguments() { throw new IllegalArgumentException("Account cannot be null"); } - executorService.schedule(() -> { + executorService.submit(() -> { try { final var ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(NoteShareActivity.this); repository = new ShareRepository(NoteShareActivity.this, ssoAcc); @@ -123,7 +121,7 @@ private void initializeArguments() { } catch (Exception e) { throw new RuntimeException(e); } - }, 0, TimeUnit.MICROSECONDS); + }); } @Override @@ -280,7 +278,8 @@ public void copyInternalLink() { return; } - showShareLinkDialog(); + final var link = createInternalLink(); + showShareLinkDialog(link); } @Override @@ -294,9 +293,14 @@ public void createPublicShareLink() { // password enforced by server, request to the user before trying to create requestPasswordForShareViaLink(true, capabilities.getAskForOptionalPassword()); } else { - executorService.schedule(() -> { - repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); - }, 0, TimeUnit.MICROSECONDS); + executorService.submit(() -> { + final var result = repository.addShare(note, ShareType.PUBLIC_LINK, "", "false", "", 0, ""); + if (result != null) { + note.setIsShared(true); + repository.updateNote(note); + runOnUiThread(this::recreate); + } + }); } } @@ -310,9 +314,7 @@ public void createSecureFileDrop() { } - private void showShareLinkDialog() { - String link = createInternalLink(); - + private void showShareLinkDialog(String link) { Intent intentToShareLink = new Intent(Intent.ACTION_SEND); intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); @@ -331,15 +333,9 @@ private String createInternalLink() { @Override public void copyLink(OCShare share) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(share.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - ClipboardUtil.copyToClipboard(this, share.getShareLink()); - } + if (!note.isShared()) { + return; } - */ if (TextUtils.isEmpty(share.getShareLink())) { copyAndShareFileLink(share.getShareLink()); @@ -352,7 +348,7 @@ private void copyAndShareFileLink(String link) { ClipboardUtil.copyToClipboard(this, link, false); Snackbar snackbar = Snackbar .make(this.findViewById(android.R.id.content), R.string.clipboard_text_copied, Snackbar.LENGTH_LONG) - .setAction(R.string.share, v -> showShareLinkDialog()); + .setAction(R.string.share, v -> showShareLinkDialog(link)); snackbar.show(); } @@ -382,7 +378,7 @@ public void refreshCapabilitiesFromDB() { } public void refreshSharesFromDB() { - executorService.schedule(() -> { + executorService.submit(() -> { try { ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); @@ -414,7 +410,7 @@ public void refreshSharesFromDB() { } catch (Exception e) { Log_OC.d(TAG, "Exception while refreshSharesFromDB: " + e); } - }, 0, TimeUnit.MICROSECONDS); + }); } private void addPublicShares(ShareeListAdapter adapter) { @@ -522,8 +518,8 @@ public void sendNewEmail(OCShare share) { @Override public void unShare(OCShare share) { - executorService.schedule(() -> { - final var result = repository.removeShare(share.getId()); + executorService.submit(() -> { + final var result = repository.removeShare(share, note); runOnUiThread(() -> { if (result) { @@ -537,7 +533,7 @@ public void unShare(OCShare share) { DisplayUtils.showSnackMessage(NoteShareActivity.this, getString(R.string.failed_the_remove_share)); } }); - }, 0, TimeUnit.MICROSECONDS); + }); } @Override From e8c4131de02eb8425a0eb0461894c6a6ed6596e6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 10 Feb 2025 14:44:28 +0100 Subject: [PATCH 21/25] fix Signed-off-by: alperozturk --- .../owncloud/notes/share/NoteShareActivity.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index deec150c0..96bbf1c9a 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -241,20 +241,24 @@ private void navigateNoteShareDetail(String shareWith, int shareType) { } private boolean accountOwnsFile() { - String displayName = account.getDisplayName(); - return TextUtils.isEmpty(displayName) || account.getAccountName().split("@")[0].equalsIgnoreCase(displayName); + if (shares.isEmpty()) { + return true; + } + + final var share = shares.get(0); + String ownerDisplayName = share.getOwnerDisplayName(); + return TextUtils.isEmpty(ownerDisplayName) || account.getAccountName().split("@")[0].equalsIgnoreCase(ownerDisplayName); } private void setShareWithYou() { if (accountOwnsFile()) { binding.sharedWithYouContainer.setVisibility(View.GONE); } else { - final var remoteId = note.getRemoteId(); - if (remoteId == null) { + if (shares.isEmpty()) { return; } - final var share = shares.get(remoteId.intValue()); + final var share = shares.get(0); binding.sharedWithYouUsername.setText( String.format(getString(R.string.note_share_activity_shared_with_you), share.getOwnerDisplayName())); From 06839b67d311e129d3c6fa254eab095d3896da5f Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 10 Feb 2025 14:45:33 +0100 Subject: [PATCH 22/25] fix Signed-off-by: alperozturk --- app/src/main/res/layout/activity_note_share.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/activity_note_share.xml b/app/src/main/res/layout/activity_note_share.xml index 7310f6938..6df68525c 100644 --- a/app/src/main/res/layout/activity_note_share.xml +++ b/app/src/main/res/layout/activity_note_share.xml @@ -77,7 +77,6 @@ app:icon="@drawable/ic_share_white_24dp" app:iconGravity="start" /> - Date: Fri, 14 Feb 2025 10:34:04 +0100 Subject: [PATCH 23/25] fix git conflicts Signed-off-by: alperozturk --- .../notes/persistence/dao/ShareDao.kt | 10 +- .../notes/share/ClientFactoryImpl.java | 84 --- .../share/CreateShareViaLinkOperation.java | 91 --- .../notes/share/NoteShareFragment.java | 692 ------------------ .../owncloud/notes/share/SyncOperation.java | 37 - .../owncloud/notes/shared/server/Server.kt | 40 - .../owncloud/notes/shared/user/User.kt | 56 -- .../notes/shared/util/ClipboardUtil.kt | 50 -- app/src/main/res/layout/activity_row.xml | 2 +- app/src/main/res/values/strings.xml | 76 +- 10 files changed, 59 insertions(+), 1079 deletions(-) delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt index 97f27cac0..15ab98f80 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/ShareDao.kt @@ -2,15 +2,15 @@ package it.niedermann.owncloud.notes.persistence.dao import androidx.room.Dao import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import it.niedermann.owncloud.notes.persistence.entity.ShareEntity @Dao interface ShareDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addShareEntities(entities: List) - @Insert - fun addShareEntity(entity: ShareEntity) - - @Query("SELECT * FROM share_table WHERE noteRemoteId = :noteRemoteId AND userName = :userName") - fun getShareEntities(noteRemoteId: Long, userName: String): List + @Query("SELECT * FROM share_table WHERE path = :path") + fun getShareEntities(path: String): List } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java b/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java deleted file mode 100644 index b9c82d25e..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/ClientFactoryImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ - -package it.niedermann.owncloud.notes.share.operations; - -import android.accounts.Account; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; -import android.app.Activity; -import android.content.Context; -import android.net.Uri; - -import com.nextcloud.common.NextcloudClient; -import com.nextcloud.common.PlainClient; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.OwnCloudClientFactory; -import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; -import com.owncloud.android.lib.common.accounts.AccountUtils; - -import java.io.IOException; - -import it.niedermann.owncloud.notes.shared.user.User; - -public class ClientFactoryImpl implements ClientFactory { - - private Context context; - - public ClientFactoryImpl(Context context) { - this.context = context; - } - - @Override - public OwnCloudClient create(User user) throws CreationException { - try { - return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), context); - } catch (OperationCanceledException | - AuthenticatorException | - IOException e) { - throw new CreationException(e); - } - } - - @Override - public NextcloudClient createNextcloudClient(User user) throws CreationException { - try { - return OwnCloudClientFactory.createNextcloudClient(user, context); - } catch (AccountUtils.AccountNotFoundException e) { - throw new CreationException(e); - } - } - - @Override - public OwnCloudClient create(Account account) - throws OperationCanceledException, AuthenticatorException, IOException, - AccountUtils.AccountNotFoundException { - return OwnCloudClientFactory.createOwnCloudClient(account, context); - } - - @Override - public OwnCloudClient create(Account account, Activity currentActivity) - throws OperationCanceledException, AuthenticatorException, IOException, - AccountUtils.AccountNotFoundException { - return OwnCloudClientFactory.createOwnCloudClient(account, context, currentActivity); - } - - @Override - public OwnCloudClient create(Uri uri, boolean followRedirects, boolean useNextcloudUserAgent) { - return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); - } - - @Override - public OwnCloudClient create(Uri uri, boolean followRedirects) { - return OwnCloudClientFactory.createOwnCloudClient(uri, context, followRedirects); - } - - @Override - public PlainClient createPlainClient() { - return new PlainClient(context); - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java deleted file mode 100644 index 509fce819..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/CreateShareViaLinkOperation.java +++ /dev/null @@ -1,91 +0,0 @@ -package it.niedermann.owncloud.notes.share; - -import java.util.ArrayList; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.FileUtils; -import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation; -import com.owncloud.android.lib.resources.shares.OCShare; -import com.owncloud.android.lib.resources.shares.ShareType; - - -import it.niedermann.owncloud.notes.shared.user.User; - -/** - * Creates a new public share for a given file - */ -public class CreateShareViaLinkOperation extends SyncOperation { - - private final String path; - private final String password; - private int permissions = OCShare.NO_PERMISSION; - - public CreateShareViaLinkOperation(String path, String password, User user) { - super(user); - - this.path = path; - this.password = password; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - CreateShareRemoteOperation createOp = new CreateShareRemoteOperation(path, - ShareType.PUBLIC_LINK, - "", - false, - password, - permissions); - createOp.setGetShareDetails(true); - RemoteOperationResult result = createOp.execute(client); - - if (result.isSuccess()) { - if (result.getData().size() > 0) { - Object item = result.getData().get(0); - if (item instanceof OCShare) { - updateData((OCShare) item); - } else { - ArrayList data = result.getData(); - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); - result.setData(data); - } - } else { - result = new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND); - } - } - - return result; - } - - private void updateData(OCShare share) { - // Update DB with the response - share.setPath(path); - if (path.endsWith(FileUtils.PATH_SEPARATOR)) { - share.setFolder(true); - } else { - share.setFolder(false); - } - - // TODO: Save share - - /* - getStorageManager().saveShare(share); - - // Update OCFile with data from share: ShareByLink and publicLink - OCFile file = getStorageManager().getFileByEncryptedRemotePath(path); - if (file != null) { - file.setSharedViaLink(true); - getStorageManager().saveFile(file); - } - */ - - } - - public String getPath() { - return this.path; - } - - public String getPassword() { - return this.password; - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java deleted file mode 100644 index 759c8a8f0..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareFragment.java +++ /dev/null @@ -1,692 +0,0 @@ -package it.niedermann.owncloud.notes.share; - -import android.Manifest; -import android.app.Activity; -import android.app.SearchManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -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.inputmethod.EditorInfo; - -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.core.content.ContextCompat; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.google.android.material.snackbar.Snackbar; -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 java.util.ArrayList; -import java.util.List; - -import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; -import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.branding.BrandedSnackbar; -import it.niedermann.owncloud.notes.databinding.FragmentNoteShareBinding; -import it.niedermann.owncloud.notes.persistence.entity.Account; -import it.niedermann.owncloud.notes.persistence.entity.Note; -import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter; -import it.niedermann.owncloud.notes.share.dialog.FileDetailSharingMenuBottomSheetDialog; -import it.niedermann.owncloud.notes.share.dialog.QuickSharingPermissionsBottomSheetDialog; -import it.niedermann.owncloud.notes.share.dialog.ShareLinkToDialog; -import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment; -import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions; -import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener; -import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig; -import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl; -import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask; -import it.niedermann.owncloud.notes.shared.user.User; -import it.niedermann.owncloud.notes.shared.util.extensions.BundleExtensionsKt; - -public class NoteShareFragment extends Fragment implements ShareeListAdapterListener, FileDetailsSharingMenuBottomSheetActions, QuickSharingPermissionsBottomSheetDialog.QuickPermissionSharingBottomSheetActions { - - private static final String TAG = "NoteShareFragment"; - private static final String ARG_NOTE = "NOTE"; - private static final String ARG_ACCOUNT = "ACCOUNT"; - private static final String ARG_USER = "USER"; - public static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; - - private FragmentNoteShareBinding binding; - private Note note; - private User user; - private Account account; - - private OnEditShareListener onEditShareListener; - private ClientFactoryImpl clientFactory; - - public static NoteShareFragment newInstance(Note note, User user, Account account) { - NoteShareFragment fragment = new NoteShareFragment(); - Bundle args = new Bundle(); - args.putSerializable(ARG_NOTE, note); - args.putSerializable(ARG_ACCOUNT, account); - args.putParcelable(ARG_USER, user); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - clientFactory = new ClientFactoryImpl(requireContext()); - - if (savedInstanceState != null) { - note = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_NOTE, Note.class); - account = BundleExtensionsKt.getSerializableArgument(savedInstanceState, ARG_ACCOUNT, Account.class); - user = BundleExtensionsKt.getParcelableArgument(savedInstanceState, ARG_USER, User.class); - } else { - Bundle arguments = getArguments(); - if (arguments != null) { - note = BundleExtensionsKt.getSerializableArgument(arguments, ARG_NOTE, Note.class); - account = BundleExtensionsKt.getSerializableArgument(arguments, ARG_ACCOUNT, Account.class); - user = BundleExtensionsKt.getParcelableArgument(arguments, ARG_USER, User.class); - } - } - - if (note == null) { - throw new IllegalArgumentException("Note cannot be null"); - } - - if (user == null) { - throw new IllegalArgumentException("Account cannot be null"); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - refreshCapabilitiesFromDB(); - refreshSharesFromDB(); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentNoteShareBinding.inflate(inflater, container, false); - - binding.sharesList.setAdapter(new ShareeListAdapter(requireActivity(), - new ArrayList<>(), - this, - user, - account)); - - binding.sharesList.setLayoutManager(new LinearLayoutManager(requireContext())); - - binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); - - setupView(); - - return binding.getRoot(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - try { - onEditShareListener = (OnEditShareListener) context; - } catch (Exception e) { - throw new IllegalArgumentException("Calling activity must implement the interface", e); - } - } - - @Override - public void onStart() { - super.onStart(); - UsersAndGroupsSearchConfig.INSTANCE.setSearchOnlyUsers(true); - } - - @Override - public void onStop() { - super.onStop(); - UsersAndGroupsSearchConfig.INSTANCE.reset(); - } - - private void setupView() { - setShareWithYou(); - - // OCFile parentFile = fileDataStorageManager.getFileById(file.getParentId()); - - setupSearchView((SearchManager) requireContext().getSystemService(Context.SEARCH_SERVICE), binding.searchView, requireActivity().getComponentName()); - // viewThemeUtils.androidx.themeToolbarSearchView(binding.searchView); - - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - - /* - if (file.canReshare()) { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_search_text)); - } else { - binding.searchView.setQueryHint(getResources().getString(R.string.note_share_fragment_resharing_not_allowed)); - binding.searchView.setInputType(InputType.TYPE_NULL); - binding.pickContactEmailBtn.setVisibility(View.GONE); - disableSearchView(binding.searchView); - } - */ - - } - - private void setupSearchView(@Nullable SearchManager searchManager, SearchView searchView, - ComponentName componentName) { - if (searchManager == null) { - searchView.setVisibility(View.GONE); - return; - } - - // assumes parent activity is the searchable activity - searchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)); - - // do not iconify the widget; expand it by default - searchView.setIconifiedByDefault(false); - - // avoid fullscreen with softkeyboard - searchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); - - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - // return true to prevent the query from being processed; - return true; - } - - @Override - public boolean onQueryTextChange(String newText) { - // leave it for the parent listener in the hierarchy / default behaviour - return false; - } - }); - } - - private void disableSearchView(View view) { - view.setEnabled(false); - - if (view instanceof ViewGroup viewGroup) { - for (int i = 0; i < viewGroup.getChildCount(); i++) { - disableSearchView(viewGroup.getChildAt(i)); - } - } - } - - // TODO: Check if note.getAccountId() return note's owner's id - private boolean accountOwnsFile() { - String noteId = String.valueOf(note.getAccountId()); - return TextUtils.isEmpty(noteId) || account.getAccountName().split("@")[0].equalsIgnoreCase(noteId); - } - - private void setShareWithYou() { - if (accountOwnsFile()) { - binding.sharedWithYouContainer.setVisibility(View.GONE); - } else { - - /* - // TODO: How to get owner display name from note? - - binding.sharedWithYouUsername.setText( - String.format(getString(R.string.note_share_fragment_shared_with_you), file.getOwnerDisplayName())); - */ - - - Glide.with(this) - .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64")) - .placeholder(R.drawable.ic_account_circle_grey_24dp) - .error(R.drawable.ic_account_circle_grey_24dp) - .apply(RequestOptions.circleCropTransform()) - .into(binding.sharedWithYouAvatar); - - binding.sharedWithYouAvatar.setVisibility(View.VISIBLE); - - /* - // TODO: Note's note? - String note = file.getNote(); - - if (!TextUtils.isEmpty(note)) { - binding.sharedWithYouNote.setText(file.getNote()); - binding.sharedWithYouNoteContainer.setVisibility(View.VISIBLE); - } else { - binding.sharedWithYouNoteContainer.setVisibility(View.GONE); - } - */ - } - } - - public void copyInternalLink() { - if (account == null) { - BrandedSnackbar.make(requireView(), getString(R.string.note_share_fragment_could_not_retrieve_url), Snackbar.LENGTH_LONG) - .setAnchorView(binding.sharesList) - .show(); - return; - } - - showShareLinkDialog(); - } - - private void showShareLinkDialog() { - String link = createInternalLink(); - - Intent intentToShareLink = new Intent(Intent.ACTION_SEND); - - intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); - intentToShareLink.setType("text/plain"); - intentToShareLink.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.note_share_fragment_subject_shared_with_you, note.getTitle())); - - String[] packagesToExclude = new String[] { requireContext().getPackageName() }; - DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude); - chooserDialog.show(getParentFragmentManager(), FTAG_CHOOSER_DIALOG); - } - - // TODO: Check account.getUrl returning base url? - private String createInternalLink() { - Uri baseUri = Uri.parse(account.getUrl()); - return baseUri + "/index.php/f/" + note.getId(); - } - - // TODO: Capabilities in notes app doesn't have following functions... - public void createPublicShareLink() { - /* - if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - - } else { - // TODO: Share files logic not suitable with notes app. How can I get remote path from remote id of the note - // Is CreateShareViaLink operation compatible? - - Intent service = new Intent(fileActivity, OperationsService.class); - service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK); - service.putExtra(OperationsService.EXTRA_ACCOUNT, fileActivity.getAccount()); - if (!TextUtils.isEmpty(password)) { - service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password); - } - service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); - mWaitingForOpId = fileActivity.getOperationsServiceBinder().queueNewOperation(service); - } - */ - - } - - public void createSecureFileDrop() { - // fileOperationsHelper.shareFolderViaSecureFileDrop(file); - } - - /* - // TODO: Cant call getFileWithLink - - public void getFileWithLink(@NonNull OCFile file, final ViewThemeUtils viewThemeUtils) { - List shares = fileActivity.getStorageManager().getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - if (shares.size() == SINGLE_LINK_SIZE) { - FileActivity.copyAndShareFileLink(fileActivity, file, shares.get(0).getShareLink(), viewThemeUtils); - } else { - if (fileActivity instanceof FileDisplayActivity) { - ((FileDisplayActivity) fileActivity).showDetails(file, 1); - } else { - showShareFile(file); - } - } - - fileActivity.refreshList(); - } - */ - private void showSendLinkTo(OCShare publicShare) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(publicShare.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - // TODO: get link from public share and pass to the function - showShareLinkDialog(); - } - } - */ - - } - - public void copyLink(OCShare share) { - /* - if (file.isSharedViaLink()) { - if (TextUtils.isEmpty(share.getShareLink())) { - fileOperationsHelper.getFileWithLink(file, viewThemeUtils); - } else { - ClipboardUtil.copyToClipboard(requireActivity(), share.getShareLink()); - } - } - */ - - } - - @Override - public void showSharingMenuActionSheet(OCShare share) { - if (getActivity() != null && !getActivity().isFinishing()) { - new FileDetailSharingMenuBottomSheetDialog(getActivity(), this, share).show(); - } - } - - @Override - public void showPermissionsDialog(OCShare share) { - new QuickSharingPermissionsBottomSheetDialog(getActivity(), this, share).show(); - } - - public void onUpdateShareInformation(RemoteOperationResult result) { - if (result.isSuccess()) { - refreshUiFromDB(); - } else { - setupView(); - } - } - - /** - * Get {@link OCShare} instance from DB and updates the UI. - */ - private void refreshUiFromDB() { - refreshSharesFromDB(); - // Updates UI with new state - setupView(); - } - - private void unshareWith(OCShare share) { - // fileOperationsHelper.unshareShare(file, share); - } - - /** - * 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(note, - 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 - public void showProfileBottomSheet(User user, String shareWith) { - if (user.getServer().getVersion().isNewerOrEqual(NextcloudVersion.nextcloud_23)) { - new RetrieveHoverCardAsyncTask(user, - account, - shareWith, - getActivity(), - clientFactory).execute(); - } - } - - /** - * Get known server capabilities from DB - */ - public void refreshCapabilitiesFromDB() { - // capabilities = fileDataStorageManager.getCapability(user.getAccountName()); - } - - /** - * Get public link from the DB to fill in the "Share link" section in the UI. Takes into account server capabilities - * before reading database. - */ - public void refreshSharesFromDB() { - /* - OCFile newFile = fileDataStorageManager.getFileById(file.getFileId()); - if (newFile != null) { - file = newFile; - } - - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - - if (adapter == null) { - BrandedSnackbar.make(requireView(), getString(R.string.could_not_retrieve_shares), Snackbar.LENGTH_LONG) - .show(); - return; - } - adapter.getShares().clear(); - - // to show share with users/groups info - List shares = fileDataStorageManager.getSharesWithForAFile(file.getRemotePath(), - user.getAccountName()); - - adapter.addShares(shares); - - if (FileDetailSharingFragmentHelper.isPublicShareDisabled(capabilities) || !file.canReshare()) { - return; - } - - // Get public share - List publicShares = fileDataStorageManager.getSharesByPathAndType(file.getRemotePath(), - ShareType.PUBLIC_LINK, - ""); - - if (publicShares.isEmpty() && containsNoNewPublicShare(adapter.getShares())) { - final OCShare ocShare = new OCShare(); - ocShare.setShareType(ShareType.NEW_PUBLIC_LINK); - publicShares.add(ocShare); - } else { - adapter.removeNewPublicShare(); - } - - adapter.addShares(publicShares); - */ - - } - - private void checkContactPermission() { - if (ContextCompat.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { - pickContactEmail(); - } else { - requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); - } - } - - private void pickContactEmail() { - Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI); - - if (intent.resolveActivity(requireContext().getPackageManager()) != null) { - onContactSelectionResultLauncher.launch(intent); - } else { - BrandedSnackbar.make(requireView(), getString(R.string.file_detail_sharing_fragment_no_contact_app_message), Snackbar.LENGTH_LONG) - .show(); - } - } - - private void handleContactResult(@NonNull Uri contactUri) { - // Define the projection to get all email addresses. - String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS}; - - Cursor cursor = requireActivity().getContentResolver().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); - 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(() -> { - binding.searchView.setQuery(email, false); - binding.searchView.requestFocus(); - }); - } else { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address."); - } - } else { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as no Email found."); - } - cursor.close(); - } else { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - Log_OC.e(NoteShareFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null."); - } - } - - private boolean containsNoNewPublicShare(List shares) { - for (OCShare share : shares) { - if (share.getShareType() == ShareType.NEW_PUBLIC_LINK) { - return false; - } - } - - return true; - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putSerializable(ARG_NOTE, note); - outState.putSerializable(ARG_ACCOUNT, account); - outState.putParcelable(ARG_USER, user); - } - - public void avatarGenerated(Drawable avatarDrawable, Object callContext) { - binding.sharedWithYouAvatar.setImageDrawable(avatarDrawable); - } - - public boolean shouldCallGeneratedCallback(String tag, Object callContext) { - return false; - } - - private boolean isReshareForbidden(OCShare share) { - return false; - // return ShareType.FEDERATED == share.getShareType() || capabilities != null && capabilities.getFilesSharingResharing().isFalse(); - } - - @VisibleForTesting - public void search(String query) { - SearchView searchView = requireView().findViewById(R.id.searchView); - searchView.setQuery(query, true); - } - - @Override - public void advancedPermissions(OCShare share) { - // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_PERMISSION); - } - - - @Override - public void sendNewEmail(OCShare share) { - // modifyExistingShare(share, FileDetailsSharingProcessFragment.SCREEN_TYPE_NOTE); - } - - @Override - public void unShare(OCShare share) { - unshareWith(share); - ShareeListAdapter adapter = (ShareeListAdapter) binding.sharesList.getAdapter(); - if (adapter == null) { - BrandedSnackbar.make(requireView(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - return; - } - adapter.remove(share); - } - - @Override - public void sendLink(OCShare share) { - /* - if (file.isSharedViaLink() && !TextUtils.isEmpty(share.getShareLink())) { - FileDisplayActivity.showShareLinkDialog(fileActivity, file, share.getShareLink()); - } else { - showSendLinkTo(share); - } - */ - - } - - @Override - public void addAnotherLink(OCShare share) { - createPublicShareLink(); - } - - private void modifyExistingShare(OCShare share, int screenTypePermission) { - // onEditShareListener.editExistingShare(share, screenTypePermission, !isReshareForbidden(share), capabilities.getVersion().isNewerOrEqual(OwnCloudVersion.nextcloud_18)); - } - - @Override - public void onQuickPermissionChanged(OCShare share, int permission) { - // fileOperationsHelper.setPermissionsToShare(share, permission); - } - - //launcher for contact permission - private final ActivityResultLauncher requestContactPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - if (isGranted) { - pickContactEmail(); - } else { - BrandedSnackbar.make(binding.getRoot(), getString(R.string.contact_no_permission), Snackbar.LENGTH_LONG) - .show(); - } - }); - - //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) { - BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - return; - } - - Uri contactUri = intent.getData(); - if (contactUri == null) { - BrandedSnackbar.make(binding.getRoot(), getString(R.string.email_pick_failed), Snackbar.LENGTH_LONG) - .show(); - return; - } - - handleContactResult(contactUri); - - } - }); - - public interface OnEditShareListener { - void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown, - boolean isExpiryDateShown); - - void onShareProcessClosed(); - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java b/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java deleted file mode 100644 index 62eb8661a..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/SyncOperation.java +++ /dev/null @@ -1,37 +0,0 @@ -package it.niedermann.owncloud.notes.share; - - -import android.content.Context; -import android.os.Handler; - -import com.nextcloud.common.NextcloudClient; -import com.nextcloud.common.User; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; - -import androidx.annotation.NonNull; - -public abstract class SyncOperation extends RemoteOperation { - private final User user; - - public SyncOperation(@NonNull User user) { - this.user = user; - } - - public RemoteOperationResult execute(Context context) { - return super.execute(user, context); - } - - public RemoteOperationResult execute(@NonNull NextcloudClient client) { - return run(client); - } - - public Thread execute(OwnCloudClient client, - OnRemoteOperationListener listener, - Handler listenerHandler) { - return super.execute(client, listener, listenerHandler); - } - -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt deleted file mode 100644 index be773c7f7..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/server/Server.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-FileCopyrightText: 2019 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package it.niedermann.owncloud.notes.shared.server - -import android.os.Parcel -import android.os.Parcelable -import com.owncloud.android.lib.resources.status.OwnCloudVersion -import java.net.URI - -/** - * This object provides all information necessary to interact - * with backend server. - */ -data class Server(val uri: URI, val version: OwnCloudVersion) : Parcelable { - - constructor(source: Parcel) : this( - source.readSerializable() as URI, - source.readParcelable(OwnCloudVersion::class.java.classLoader) as OwnCloudVersion - ) - - override fun describeContents() = 0 - - override fun writeToParcel(dest: Parcel, flags: Int) = with(dest) { - writeSerializable(uri) - writeParcelable(version, 0) - } - - companion object { - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): Server = Server(source) - override fun newArray(size: Int): Array = arrayOfNulls(size) - } - } -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt deleted file mode 100644 index b68c4598d..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/user/User.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2020 Chris Narkiewicz - * SPDX-FileCopyrightText: 2020 Nextcloud GmbH - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ -package it.niedermann.owncloud.notes.shared.user - -import android.accounts.Account -import android.os.Parcelable -import com.owncloud.android.lib.common.OwnCloudAccount -import it.niedermann.owncloud.notes.shared.server.Server - -interface User : Parcelable, com.nextcloud.common.User { - override val accountName: String - val server: Server - - /** - * This is temporary helper method created to facilitate incremental refactoring. - * Code using legacy platform Account can be partially converted to instantiate User - * object and use account instance when required. - * - * This method calls will allow tracing code awaiting further refactoring. - * - * @return Account instance that is associated with this User object. - */ - @Deprecated("Temporary workaround") - override fun toPlatformAccount(): Account - - /** - * This is temporary helper method created to facilitate incremental refactoring. - * Code using legacy ownCloud account can be partially converted to instantiate User - * object and use account instance when required. - * - * This method calls will allow tracing code awaiting further refactoring. - * - * @return OwnCloudAccount instance that is associated with this User object. - */ - @Deprecated("Temporary workaround") - fun toOwnCloudAccount(): OwnCloudAccount - - /** - * Compare account names, case insensitive. - * - * @return true if account names are same, false otherwise - */ - fun nameEquals(user: User?): Boolean - - /** - * Compare account names, case insensitive. - * - * @return true if account names are same, false otherwise - */ - fun nameEquals(accountName: CharSequence?): Boolean -} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt deleted file mode 100644 index cb6c5e14f..000000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/ClipboardUtil.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2018 Andy Scherzinger - * SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only - */ - -package it.niedermann.owncloud.notes.shared.util - -import android.app.Activity -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.text.TextUtils -import android.widget.Toast -import com.owncloud.android.lib.common.utils.Log_OC -import it.niedermann.owncloud.notes.R - -/** - * Helper implementation to copy a string into the system clipboard. - */ -object ClipboardUtil { - private val TAG = ClipboardUtil::class.java.name - - @JvmStatic - @JvmOverloads - @Suppress("TooGenericExceptionCaught") - fun copyToClipboard(activity: Activity, text: String?, showToast: Boolean = true) { - if (!TextUtils.isEmpty(text)) { - try { - val clip = ClipData.newPlainText( - activity.getString( - R.string.clipboard_label, - activity.getString(R.string.app_name) - ), - text - ) - (activity.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(clip) - if (showToast) { - Toast.makeText(activity, R.string.clipboard_text_copied, Toast.LENGTH_SHORT).show() - } - } catch (e: Exception) { - Toast.makeText(activity, R.string.clipboard_unexpected_error, Toast.LENGTH_SHORT).show() - Log_OC.e(TAG, "Exception caught while copying to clipboard", e) - } - } else { - Toast.makeText(activity, R.string.clipboard_no_text_to_copy, Toast.LENGTH_SHORT).show() - } - } -} diff --git a/app/src/main/res/layout/activity_row.xml b/app/src/main/res/layout/activity_row.xml index 8d6e4d150..ffb8e2e6c 100644 --- a/app/src/main/res/layout/activity_row.xml +++ b/app/src/main/res/layout/activity_row.xml @@ -23,7 +23,7 @@ android:layout_height="@dimen/default_icon_size" android:layout_gravity="center_vertical" android:layout_marginEnd="@dimen/spacer_2x" - android:contentDescription="@string/note_share_fragment_activity_icon_content_description" /> + android:contentDescription="@string/note_share_activity_activity_icon_content_description" /> Choose a category - + + com.nextcloud.android.providers.UsersAndGroupsSearchProvider + com.nextcloud.android.providers.UsersAndGroupsSearchProvider.action.SHARE_WITH + + + @string/link_share_view_only @string/link_share_allow_upload_and_editing @string/link_share_file_drop - + @string/link_share_view_only @string/link_share_editing - note activity icon - note share icon - note share external icon - note share copy icon - note share more icon - note share icon - note share user icon - note share contact icon - Shared with you by %1$s - Name, Federated Cloud ID or email address… - Share link - Policy or permissions prevent resharing - Could not retrieve URL - \"%1$s\" has been shared with you - Contact permission is required. - You must enter a password - Password - Enter an optional password - Skip - Cancel - + + + note activity icon + note share icon + note share external icon + note share copy icon + note share more icon + note share icon + note share user icon + note share contact icon + Shared with you by %1$s + Share note + Name, Federated Cloud ID or email address… + Share link + Policy or permissions prevent resharing + Could not retrieve URL + \"%1$s\" has been shared with you + Contact permission is required. + + + Advanced Settings + Hide download + Note to recipient + Note + Next + Share and Copy Link + Confirm + Set Note + Send Share + Name + Link Name + You must enter a password + Password + Please select at least one permission to share. + Label cannot be empty + Cancel + Failed to create a share + + + Enter an optional password + Skip + Enter a password OK Delete @@ -76,6 +102,7 @@ No App available to handle mail address No actions for this user + Failed to remove share Failed to pick email address. Failed to update UI No app available to select contacts @@ -124,6 +151,9 @@ %1$s (remote) %1$s (conversation) on %1$s + (remote) + + Copy link Share From 097a1c25192cbfb173b5dbe7be9b734ad46241fb Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 14 Feb 2025 10:36:53 +0100 Subject: [PATCH 24/25] remove note Signed-off-by: alperozturk --- .../persistence/sync/CapabilitiesDeserializer.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java index 7cc6679d2..a382d18f4 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java @@ -43,20 +43,6 @@ public class CapabilitiesDeserializer implements JsonDeserializer private static final String CAPABILITIES_FILES_SHARING = "files_sharing"; private static final String VERSION = "version"; - /* - if (capabilities != null && (capabilities.getFilesSharingPublicPasswordEnforced().isTrue() || - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue())) { - // password enforced by server, request to the user before trying to create - requestPasswordForShareViaLink(true, - capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); - - } else { - // create without password if not enforced by server or we don't know if enforced; - fileOperationsHelper.shareFileViaPublicShare(file, null); - } - - password -> {JsonObject@32644} "{"enforced":false,"askForOptionalPassword":false}" - */ @Override public Capabilities deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { final var response = new Capabilities(); From bd3ef72f6877398014431db2bf23103c8633d7f6 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 17 Feb 2025 17:02:28 +0100 Subject: [PATCH 25/25] check resharing Signed-off-by: alperozturk --- .../owncloud/notes/edit/BaseNoteFragment.java | 2 +- .../owncloud/notes/edit/EditNoteActivity.java | 2 +- .../notes/persistence/NotesRepository.java | 8 +-- .../notes/persistence/dao/NoteDao.java | 18 +++---- .../notes/persistence/entity/Note.java | 19 +++++-- .../sync/CapabilitiesDeserializer.java | 2 + .../notes/share/NoteShareActivity.java | 51 ++++++++++++++----- .../notes/shared/model/Capabilities.java | 9 ++++ 8 files changed, 81 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java index fbbf86c2a..903bce104 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java @@ -122,7 +122,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (content == null) { throw new IllegalArgumentException(PARAM_NOTE_ID + " is not given, argument " + PARAM_NEWNOTE + " is missing and " + PARAM_CONTENT + " is missing."); } else { - note = new Note(-1, null, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, getString(R.string.category_readonly), false, null, DBStatus.VOID, -1, "", 0, false); + note = new Note(-1, null, Calendar.getInstance(), NoteUtil.generateNoteTitle(content), content, getString(R.string.category_readonly), false, null, DBStatus.VOID, -1, "", 0, false, false); requireActivity().runOnUiThread(() -> onNoteLoaded(note)); requireActivity().invalidateOptionsMenu(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java index 345b278d7..ee24c2820 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/EditNoteActivity.java @@ -302,7 +302,7 @@ private void launchNewNote() { if (content == null) { content = ""; } - final var newNote = new Note(null, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, this), content, categoryTitle, favorite, null, false); + final var newNote = new Note(null, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, this), content, categoryTitle, favorite, null, false, false); fragment = getNewNoteFragment(newNote); replaceFragment(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java index fa9eb594d..cffa91561 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java @@ -480,7 +480,7 @@ public NotesListWidgetData getNoteListWidgetData(int appWidgetId) { @NonNull @MainThread public LiveData addNoteAndSync(Account account, Note note) { - final var entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0, note.isShared()); + final var entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0, note.isShared(), note.getReadonly()); final var ret = new MutableLiveData(); executor.submit(() -> ret.postValue(addNote(account.getId(), entity))); return map(ret, newNote -> { @@ -508,7 +508,7 @@ public Note addNote(long accountId, @NonNull Note note) { @MainThread public LiveData moveNoteToAnotherAccount(Account account, @NonNull Note note) { - final var fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null, note.isShared()); + final var fullNote = new Note(null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), null, note.isShared(), note.getReadonly()); fullNote.setStatus(DBStatus.LOCAL_EDITED); deleteNoteAndSync(account, note.getId()); return addNoteAndSync(account, fullNote); @@ -570,7 +570,7 @@ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNo // https://github.com/nextcloud/notes-android/issues/1198 @Nullable final Long remoteId = db.getNoteDao().getRemoteId(oldNote.getId()); if (newContent == null) { - newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY(), oldNote.isShared()); + newNote = new Note(oldNote.getId(), remoteId, oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY(), oldNote.isShared(), oldNote.getReadonly()); } else { final String title; if (newTitle != null) { @@ -584,7 +584,7 @@ public Note updateNoteAndSync(@NonNull Account localAccount, @NonNull Note oldNo title = oldNote.getTitle(); } } - newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY(), oldNote.isShared()); + newNote = new Note(oldNote.getId(), remoteId, Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY(), oldNote.isShared(), oldNote.getReadonly()); } int rows = db.getNoteDao().updateNote(newNote); // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly. diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java index 2f04c77e9..6c1b48634 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java @@ -38,14 +38,14 @@ public interface NoteDao { String getNoteById = "SELECT * FROM NOTE WHERE id = :id"; String count = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId"; String countFavorites = "SELECT COUNT(*) FROM NOTE WHERE status != 'LOCAL_DELETED' AND accountId = :accountId AND favorite = 1"; - String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC"; - String searchRecentLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; - String searchFavoritesByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY modified DESC"; - String searchFavoritesLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY title COLLATE LOCALIZED ASC"; - String searchUncategorizedByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, modified DESC"; - String searchUncategorizedLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; - String searchCategoryByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, modified DESC"; - String searchCategoryLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, title COLLATE LOCALIZED ASC"; + String searchRecentByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, modified DESC"; + String searchRecentLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; + String searchFavoritesByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY modified DESC"; + String searchFavoritesLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND favorite = 1 ORDER BY title COLLATE LOCALIZED ASC"; + String searchUncategorizedByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, modified DESC"; + String searchUncategorizedLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND category = '' ORDER BY favorite DESC, title COLLATE LOCALIZED ASC"; + String searchCategoryByModified = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, modified DESC"; + String searchCategoryLexicographically = "SELECT id, remoteId, accountId, title, favorite, isShared, readonly, excerpt, modified, category, status, '' as eTag, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND (title LIKE :query OR content LIKE :query) AND (category = :category OR category LIKE :category || '/%') ORDER BY category, favorite DESC, title COLLATE LOCALIZED ASC"; @Query(getNoteById) LiveData getNoteById$(long id); @@ -141,7 +141,7 @@ public interface NoteDao { * Gets a list of {@link Note} objects with filled {@link Note#id} and {@link Note#remoteId}, * where {@link Note#remoteId} is not null */ - @Query("SELECT id, remoteId, 0 as accountId, '' as title, 0 as favorite, 0 as isShared, '' as excerpt, 0 as modified, '' as eTag, 0 as status, '' as category, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND remoteId IS NOT NULL") + @Query("SELECT id, remoteId, 0 as accountId, '' as title, 0 as favorite, 0 as isShared, 0 as readonly, '' as excerpt, 0 as modified, '' as eTag, 0 as status, '' as category, '' as content, 0 as scrollY FROM NOTE WHERE accountId = :accountId AND status != 'LOCAL_DELETED' AND remoteId IS NOT NULL") List getRemoteIdAndId(long accountId); /** diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java index 84756ebbc..010493abf 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/entity/Note.java @@ -39,6 +39,7 @@ @Index(name = "IDX_NOTE_CATEGORY", value = "category"), @Index(name = "IDX_NOTE_FAVORITE", value = "favorite"), @Index(name = "IDX_NOTE_IS_SHARED", value = "isShared"), + @Index(name = "IDX_READONLY", value = "readonly"), @Index(name = "IDX_NOTE_MODIFIED", value = "modified"), @Index(name = "IDX_NOTE_REMOTEID", value = "remoteId"), @Index(name = "IDX_NOTE_STATUS", value = "status") @@ -86,6 +87,10 @@ public class Note implements Serializable, Item { @ColumnInfo(defaultValue = "0") private boolean isShared = false; + @Expose + @ColumnInfo(defaultValue = "0") + private boolean readonly = false; + @Expose @Nullable @SerializedName("etag") @@ -103,7 +108,7 @@ public Note() { } @Ignore - public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String eTag, boolean isShared) { + public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String eTag, boolean isShared, boolean readonly) { this.remoteId = remoteId; this.title = title; this.modified = modified; @@ -115,8 +120,8 @@ public Note(@Nullable Long remoteId, @Nullable Calendar modified, @NonNull Strin } @Ignore - public Note(long id, @Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String etag, @NonNull DBStatus status, long accountId, @NonNull String excerpt, int scrollY, boolean isShared) { - this(remoteId, modified, title, content, category, favorite, etag, isShared); + public Note(long id, @Nullable Long remoteId, @Nullable Calendar modified, @NonNull String title, @NonNull String content, @NonNull String category, boolean favorite, @Nullable String etag, @NonNull DBStatus status, long accountId, @NonNull String excerpt, int scrollY, boolean isShared, boolean readonly) { + this(remoteId, modified, title, content, category, favorite, etag, isShared, readonly); this.id = id; this.status = status; this.accountId = accountId; @@ -202,6 +207,14 @@ public void setContent(@NonNull String content) { this.content = content; } + public boolean getReadonly() { + return readonly; + } + + public void setReadonly(boolean value) { + readonly = value; + } + public boolean getFavorite() { return favorite; } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java index a382d18f4..42efdcb18 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/CapabilitiesDeserializer.java @@ -74,9 +74,11 @@ public Capabilities deserialize(JsonElement json, Type typeOfT, JsonDeserializat final var password = publicObject.getAsJsonObject("password"); final var enforced = password.getAsJsonPrimitive("enforced"); final var askForOptionalPassword = password.getAsJsonPrimitive("askForOptionalPassword"); + final var isReSharingAllowed = filesSharing.getAsJsonPrimitive("resharing"); response.setPublicPasswordEnforced(enforced.getAsBoolean()); response.setAskForOptionalPassword(askForOptionalPassword.getAsBoolean()); + response.setReSharingAllowed(isReSharingAllowed.getAsBoolean()); } if (capabilities.has(CAPABILITIES_NOTES)) { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java index 96bbf1c9a..706bfac77 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/share/NoteShareActivity.java @@ -14,6 +14,7 @@ import android.text.InputType; import android.text.TextUtils; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import androidx.activity.result.ActivityResultLauncher; @@ -114,7 +115,12 @@ private void initializeArguments() { binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission()); binding.btnShareButton.setOnClickListener(v -> ShareUtil.openShareDialog(this, note.getTitle(), note.getContent())); - setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); + if (note.getReadonly()) { + setupReadOnlySearchView(); + } else { + setupSearchView((SearchManager) getSystemService(Context.SEARCH_SERVICE), getComponentName()); + } + refreshCapabilitiesFromDB(); refreshSharesFromDB(); }); @@ -136,6 +142,24 @@ public void onStop() { UsersAndGroupsSearchConfig.INSTANCE.reset(); } + private void disableSearchView(View view) { + view.setEnabled(false); + + if (view instanceof ViewGroup viewGroup) { + for (int i = 0; i < viewGroup.getChildCount(); i++) { + disableSearchView(viewGroup.getChildAt(i)); + } + } + } + + private void setupReadOnlySearchView() { + binding.searchView.setIconifiedByDefault(false); + binding.searchView.setQueryHint(getResources().getString(R.string.note_share_activity_resharing_not_allowed)); + binding.searchView.setInputType(InputType.TYPE_NULL); + binding.pickContactEmailBtn.setVisibility(View.GONE); + disableSearchView(binding.searchView); + } + private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) { if (searchManager == null) { binding.searchView.setVisibility(View.GONE); @@ -182,24 +206,26 @@ public boolean onQueryTextChange(String newText) { } final var isFederationShareAllowed = capabilities.getFederationShare(); - try(var cursor = provider.searchForUsersOrGroups(newText, isFederationShareAllowed)) { - runOnUiThread(() -> { - { - if (cursor == null || cursor.getCount() == 0) { - return; - } + try { + var cursor = provider.searchForUsersOrGroups(newText, isFederationShareAllowed); - if (binding.searchView.getVisibility() == View.VISIBLE) { - suggestionAdapter.swapCursor(cursor); - } + if (cursor == null || cursor.getCount() == 0) { + return; + } - binding.progressBar.setVisibility(View.GONE); + runOnUiThread(() -> { + if (binding.searchView.getVisibility() == View.VISIBLE) { + suggestionAdapter.swapCursor(cursor); } + + binding.progressBar.setVisibility(View.GONE); }); + } catch (Exception e) { Log_OC.d(TAG, "Exception setupSearchView.onQueryTextChange: " + e); runOnUiThread(() -> binding.progressBar.setVisibility(View.GONE)); } + }, SEARCH_DELAY_MS, TimeUnit.MILLISECONDS); return false; @@ -500,7 +526,8 @@ public void onSaveInstanceState(@NonNull Bundle outState) { } private boolean isReshareForbidden(OCShare share) { - return false; + return ShareType.FEDERATED == share.getShareType() || + capabilities != null && !capabilities.isReSharingAllowed(); } @VisibleForTesting diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java index efb9e4918..b7a8aaed2 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/model/Capabilities.java @@ -41,6 +41,15 @@ public class Capabilities implements Serializable { private boolean publicPasswordEnforced; private boolean askForOptionalPassword; + private boolean isReSharingAllowed; + + public boolean isReSharingAllowed() { + return isReSharingAllowed; + } + + public void setReSharingAllowed(boolean value) { + this.isReSharingAllowed = value; + } public boolean getPublicPasswordEnforced() { return publicPasswordEnforced;