diff --git a/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialog.png b/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialog.png new file mode 100644 index 0000000000..bd3d20143b Binary files /dev/null and b/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialog.png differ diff --git a/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendShareDialogTest_showDialog.png b/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendShareDialogTest_showDialog.png new file mode 100644 index 0000000000..ee28ab2ac6 Binary files /dev/null and b/screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendShareDialogTest_showDialog.png differ diff --git a/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFiles.png b/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFiles.png index 56d59ec8b0..0e493a97c0 100644 Binary files a/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFiles.png and b/screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showFiles.png differ diff --git a/src/androidTest/java/com/owncloud/android/ui/dialog/SendFilesDialogTest.kt b/src/androidTest/java/com/owncloud/android/ui/dialog/SendFilesDialogTest.kt new file mode 100644 index 0000000000..dc3bb17efa --- /dev/null +++ b/src/androidTest/java/com/owncloud/android/ui/dialog/SendFilesDialogTest.kt @@ -0,0 +1,64 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Tobias Kaminsky + * Copyright (C) 2020 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.ui.dialog + +import androidx.fragment.app.FragmentManager +import androidx.test.espresso.intent.rule.IntentsTestRule +import androidx.test.platform.app.InstrumentationRegistry +import com.nextcloud.client.TestActivity +import com.owncloud.android.AbstractIT +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.utils.ScreenshotTest +import org.junit.Rule +import org.junit.Test + +class SendFilesDialogTest : AbstractIT() { + @get:Rule + val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false) + + @Test + @ScreenshotTest + fun showDialog() { + val activity = testActivityRule.launchActivity(null) + + val fm: FragmentManager = activity.supportFragmentManager + val ft = fm.beginTransaction() + ft.addToBackStack(null) + + val files = setOf( + OCFile("/1.jpg").apply { + mimeType = "image/jpg" + }, + OCFile("/2.jpg").apply { + mimeType = "image/jpg" + } + ) + + val sut = SendFilesDialog.newInstance(files) + sut.show(ft, "TAG_SEND_SHARE_DIALOG") + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + shortSleep() + + sut.requireDialog().window?.decorView.let { screenshot(it) } + } +} diff --git a/src/androidTest/java/com/owncloud/android/ui/dialog/SendShareDialogTest.kt b/src/androidTest/java/com/owncloud/android/ui/dialog/SendShareDialogTest.kt new file mode 100644 index 0000000000..b6a03b1e7b --- /dev/null +++ b/src/androidTest/java/com/owncloud/android/ui/dialog/SendShareDialogTest.kt @@ -0,0 +1,60 @@ +/* + * + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Tobias Kaminsky + * Copyright (C) 2020 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.ui.dialog + +import androidx.fragment.app.FragmentManager +import androidx.test.espresso.intent.rule.IntentsTestRule +import androidx.test.platform.app.InstrumentationRegistry +import com.nextcloud.client.TestActivity +import com.owncloud.android.AbstractIT +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.resources.status.OCCapability +import com.owncloud.android.utils.ScreenshotTest +import org.junit.Rule +import org.junit.Test + +class SendShareDialogTest : AbstractIT() { + @get:Rule + val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false) + + @Test + @ScreenshotTest + fun showDialog() { + val activity = testActivityRule.launchActivity(null) + + val fm: FragmentManager = activity.supportFragmentManager + val ft = fm.beginTransaction() + ft.addToBackStack(null) + + val file = OCFile("/1.jpg").apply { + mimeType = "image/jpg" + } + + val sut = SendShareDialog.newInstance(file, false, OCCapability()) + sut.show(ft, "TAG_SEND_SHARE_DIALOG") + + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + shortSleep() + + sut.requireDialog().window?.decorView.let { screenshot(it) } + } +} diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 8e06f2a9e3..576c4996a6 100644 --- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -58,6 +58,7 @@ import androidx.annotation.Nullable; public class FileMenuFilter { private static final int SINGLE_SELECT_ITEMS = 1; + public static final String SEND_OFF = "off"; private int numberOfAllFiles; private Collection files; @@ -203,6 +204,7 @@ public class FileMenuFilter { filterCancelSync(toShow, toHide, synchronizing); filterSync(toShow, toHide, synchronizing); filterShareFile(toShow, toHide, capability); + filterSendFiles(toShow, toHide); filterDetails(toShow, toHide); filterFavorite(toShow, toHide, synchronizing); filterUnfavorite(toShow, toHide, synchronizing); @@ -222,6 +224,15 @@ public class FileMenuFilter { } } + private void filterSendFiles(List toShow, List toHide) { + if (containsEncryptedFile() || isSingleSelection() || overflowMenu || !anyFileDown() || + SEND_OFF.equalsIgnoreCase(context.getString(R.string.send_files_to_other_apps))) { + toHide.add(R.id.action_send_file); + } else { + toShow.add(R.id.action_send_file); + } + } + private void filterDetails(Collection toShow, Collection toHide) { if (isSingleSelection()) { toShow.add(R.id.action_see_details); diff --git a/src/main/java/com/owncloud/android/ui/dialog/SendFilesDialog.java b/src/main/java/com/owncloud/android/ui/dialog/SendFilesDialog.java new file mode 100644 index 0000000000..a90ab02dd8 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/dialog/SendFilesDialog.java @@ -0,0 +1,160 @@ +package com.owncloud.android.ui.dialog; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.ui.adapter.SendButtonAdapter; +import com.owncloud.android.ui.components.SendButtonData; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/* + * Nextcloud Android client application + * + * @author Tobias Kaminsky + * Copyright (C) 2020 Tobias Kaminsky + * Copyright (C) 2020 Nextcloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +public class SendFilesDialog extends BottomSheetDialogFragment { + + private static final String KEY_OCFILES = "KEY_OCFILES"; + + private OCFile[] files; + + public static SendFilesDialog newInstance(Set files) { + + SendFilesDialog dialogFragment = new SendFilesDialog(); + + Bundle args = new Bundle(); + args.putParcelableArray(KEY_OCFILES, files.toArray(new OCFile[0])); + dialogFragment.setArguments(args); + + return dialogFragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // keep the state of the fragment on configuration changes + setRetainInstance(true); + + files = (OCFile[]) requireArguments().getParcelableArray(KEY_OCFILES); + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.send_files_fragment, container, false); + + // populate send apps + Intent sendIntent = createSendIntent(); + + List sendButtonDataList = setupSendButtonData(sendIntent); + + SendButtonAdapter.ClickListener clickListener = setupSendButtonClickListener(sendIntent); + + RecyclerView sendButtonsView = view.findViewById(R.id.send_button_recycler_view); + sendButtonsView.setHasFixedSize(true); + sendButtonsView.setLayoutManager(new GridLayoutManager(getActivity(), 4)); + sendButtonsView.setAdapter(new SendButtonAdapter(sendButtonDataList, clickListener)); + + return view; + } + + @NonNull + private SendButtonAdapter.ClickListener setupSendButtonClickListener(Intent sendIntent) { + return sendButtonDataData -> { + String packageName = sendButtonDataData.getPackageName(); + String activityName = sendButtonDataData.getActivityName(); + + sendIntent.setComponent(new ComponentName(packageName, activityName)); + requireActivity().startActivity(Intent.createChooser(sendIntent, getString(R.string.send))); + + dismiss(); + }; + } + + @NonNull + private List setupSendButtonData(Intent sendIntent) { + Drawable icon; + SendButtonData sendButtonData; + CharSequence label; + List matches = requireActivity().getPackageManager().queryIntentActivities(sendIntent, 0); + List sendButtonDataList = new ArrayList<>(matches.size()); + for (ResolveInfo match : matches) { + icon = match.loadIcon(requireActivity().getPackageManager()); + label = match.loadLabel(requireActivity().getPackageManager()); + sendButtonData = new SendButtonData(icon, label, + match.activityInfo.packageName, + match.activityInfo.name); + + sendButtonDataList.add(sendButtonData); + } + return sendButtonDataList; + } + + @NonNull + private Intent createSendIntent() { + Intent sendIntent = new Intent(Intent.ACTION_SEND_MULTIPLE); + sendIntent.setType(getUniqueMimetype()); + sendIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, getExposedFileUris()); + sendIntent.putExtra(Intent.ACTION_SEND, true); + return sendIntent; + } + + @Nullable + private String getUniqueMimetype() { + String mimetype = files[0].getMimeType(); + + for (OCFile file : files) { + if (!mimetype.equals(file.getMimeType())) { + return null; + } + } + + return mimetype; + } + + private ArrayList getExposedFileUris() { + ArrayList uris = new ArrayList<>(); + + for (OCFile file : files) { + uris.add(file.getExposedFileUri(requireContext())); + } + + return uris; + } +} diff --git a/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java b/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java index 3fd2d0fbd6..8d45db171a 100644 --- a/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java +++ b/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java @@ -241,11 +241,15 @@ public class SendShareDialog extends BottomSheetDialogFragment { @NonNull private List setupSendButtonData(Intent sendIntent) { - List sendButtonDataList = new ArrayList<>(); - for (ResolveInfo match : getActivity().getPackageManager().queryIntentActivities(sendIntent, 0)) { - Drawable icon = match.loadIcon(getActivity().getPackageManager()); - CharSequence label = match.loadLabel(getActivity().getPackageManager()); - SendButtonData sendButtonData = new SendButtonData(icon, label, + Drawable icon; + SendButtonData sendButtonData; + CharSequence label; + List matches = requireActivity().getPackageManager().queryIntentActivities(sendIntent, 0); + List sendButtonDataList = new ArrayList<>(matches.size()); + for (ResolveInfo match : matches) { + icon = match.loadIcon(requireActivity().getPackageManager()); + label = match.loadLabel(requireActivity().getPackageManager()); + sendButtonData = new SendButtonData(icon, label, match.activityInfo.packageName, match.activityInfo.name); diff --git a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index b5c4656a1d..99a04fbd48 100644 --- a/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1197,6 +1197,9 @@ public class OCFileListFragment extends ExtendedListFragment implements selectAllFiles(false); return true; } + case R.id.action_send_file: + mContainerActivity.getFileOperationsHelper().sendFiles(checkedFiles); + return true; default: return false; } diff --git a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index e81843e81c..11178e080d 100755 --- a/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -73,6 +73,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.RichDocumentsEditorWebView; import com.owncloud.android.ui.activity.ShareActivity; import com.owncloud.android.ui.activity.TextEditorWebView; +import com.owncloud.android.ui.dialog.SendFilesDialog; import com.owncloud.android.ui.dialog.SendShareDialog; import com.owncloud.android.ui.events.EncryptionEvent; import com.owncloud.android.ui.events.FavoriteEvent; @@ -97,6 +98,7 @@ import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -703,6 +705,16 @@ public class FileOperationsHelper { mSendShareDialog.show(ft, "TAG_SEND_SHARE_DIALOG"); } + public void sendFiles(Set files) { + // Show dialog + FragmentManager fm = fileActivity.getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + ft.addToBackStack(null); + + SendFilesDialog sendFilesDialog = SendFilesDialog.newInstance(files); + sendFilesDialog.show(ft, "TAG_SEND_SHARE_DIALOG"); + } + public void sendShareFile(OCFile file) { sendShareFile(file, !file.canReshare()); } diff --git a/src/main/res/layout/send_files_fragment.xml b/src/main/res/layout/send_files_fragment.xml new file mode 100644 index 0000000000..07b1d96acc --- /dev/null +++ b/src/main/res/layout/send_files_fragment.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/src/main/res/menu/item_file.xml b/src/main/res/menu/item_file.xml index 26a5e3ee76..02955f6cf1 100644 --- a/src/main/res/menu/item_file.xml +++ b/src/main/res/menu/item_file.xml @@ -75,7 +75,7 @@ android:title="@string/stream" app:showAsAction="never" android:showAsAction="never" - android:orderInCategory="1"/> + android:orderInCategory="1" /> + +