Sfoglia il codice sorgente

Add send multiple files, if downloaded

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 4 anni fa
parent
commit
cfdd3d4588

BIN
screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendFilesDialogTest_showDialog.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.dialog.SendShareDialogTest_showDialog.png


+ 64 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+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) }
+    }
+}

+ 60 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+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) }
+    }
+}

+ 10 - 0
src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -203,6 +203,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 +223,15 @@ public class FileMenuFilter {
         }
     }
 
+    private void filterSendFiles(List<Integer> toShow, List<Integer> toHide) {
+        if (containsEncryptedFile() || isSingleSelection() || overflowMenu || !anyFileDown() ||
+            "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<Integer> toShow, Collection<Integer> toHide) {
         if (isSingleSelection()) {
             toShow.add(R.id.action_see_details);

+ 159 - 0
src/main/java/com/owncloud/android/ui/dialog/SendFilesDialog.java

@@ -0,0 +1,159 @@
+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 <http://www.gnu.org/licenses/>.
+ */
+public class SendFilesDialog extends BottomSheetDialogFragment {
+
+    private static final String KEY_OCFILES = "KEY_OCFILES";
+
+    private OCFile[] files;
+
+    public static SendFilesDialog newInstance(Set<OCFile> 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<SendButtonData> 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<SendButtonData> setupSendButtonData(Intent sendIntent) {
+        List<SendButtonData> sendButtonDataList = new ArrayList<>();
+        Drawable icon;
+        SendButtonData sendButtonData;
+        CharSequence label;
+        for (ResolveInfo match : requireActivity().getPackageManager().queryIntentActivities(sendIntent, 0)) {
+            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<Uri> getExposedFileUris() {
+        ArrayList<Uri> uris = new ArrayList<>();
+
+        for (OCFile file : files) {
+            uris.add(file.getExposedFileUri(requireContext()));
+        }
+
+        return uris;
+    }
+}

+ 3 - 0
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;
         }

+ 12 - 0
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<OCFile> 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());
     }

+ 36 - 0
src/main/res/layout/send_files_fragment.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ 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 <http://www.gnu.org/licenses/>.
+-->
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/send_button_recycler_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            tools:listitem="@layout/send_button" />
+    </RelativeLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 8 - 1
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" />
 
     <item
         android:id="@+id/action_send_share_file"
@@ -84,6 +84,13 @@
         android:showAsAction="never"
         android:icon="@drawable/ic_share" />
 
+    <item
+        android:id="@+id/action_send_file"
+        android:title="@string/common_send"
+        app:showAsAction="never"
+        android:showAsAction="never"
+        android:icon="@drawable/ic_share" />
+
     <item
         android:id="@+id/action_open_file_with"
         android:title="@string/actionbar_open_with"