Эх сурвалжийг харах

add ability to "share to" files to nextcloud talk app

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 4 жил өмнө
parent
commit
36fdd6d82b

+ 13 - 0
app/src/main/AndroidManifest.xml

@@ -63,6 +63,7 @@
         android:name="android.permission.USE_CREDENTIALS"
         android:maxSdkVersion="22" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <uses-permission android:name="android.permission.USE_BIOMETRIC" />
@@ -96,6 +97,18 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
 
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="*/*" />
+            </intent-filter>
+
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />

+ 0 - 2
app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt

@@ -307,9 +307,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
 
     override fun onNewIntent(intent: Intent) {
         super.onNewIntent(intent)
-
         handleActionFromContact(intent)
-
         if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
             if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
                 router!!.pushController(

+ 28 - 11
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -26,6 +26,7 @@ import android.app.Activity.RESULT_OK
 import android.content.ClipData
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.content.res.Resources
 import android.graphics.Bitmap
 import android.graphics.PorterDuff
@@ -263,6 +264,8 @@ class ChatController(args: Bundle) :
     var pastPreconditionFailed = false
     var futurePreconditionFailed = false
 
+    val filesToUpload: MutableList<String> = ArrayList()
+
     init {
         setHasOptionsMenu(true)
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@@ -683,27 +686,27 @@ class ChatController(args: Bundle) :
             if (resultCode == RESULT_OK) {
                 try {
                     checkNotNull(intent)
-                    val files: MutableList<String> = ArrayList()
+                    filesToUpload.clear()
                     intent.clipData?.let {
                         for (index in 0 until it.itemCount) {
-                            files.add(it.getItemAt(index).uri.toString())
+                            filesToUpload.add(it.getItemAt(index).uri.toString())
                         }
                     } ?: run {
                         checkNotNull(intent.data)
                         intent.data.let {
-                            files.add(intent.data.toString())
+                            filesToUpload.add(intent.data.toString())
                         }
                     }
-                    require(files.isNotEmpty())
+                    require(filesToUpload.isNotEmpty())
 
                     val filenamesWithLinebreaks = StringBuilder("\n")
 
-                    for (file in files) {
+                    for (file in filesToUpload) {
                         val filename = UriUtils.getFileName(Uri.parse(file), context)
                         filenamesWithLinebreaks.append(filename).append("\n")
                     }
 
-                    val confirmationQuestion = when (files.size) {
+                    val confirmationQuestion = when (filesToUpload.size) {
                         1 -> context?.resources?.getString(R.string.nc_upload_confirm_send_single)?.let {
                             String.format(it, title)
                         }
@@ -717,11 +720,11 @@ class ChatController(args: Bundle) :
                         .setTitle(confirmationQuestion)
                         .setMessage(filenamesWithLinebreaks.toString())
                         .setPositiveButton(R.string.nc_yes) { v ->
-                            uploadFiles(files)
-                            Toast.makeText(
-                                context, context?.resources?.getString(R.string.nc_upload_in_progess),
-                                Toast.LENGTH_LONG
-                            ).show()
+                            if (UploadAndShareFilesWorker.isStoragePermissionGranted(context!!)) {
+                                uploadFiles(filesToUpload)
+                            } else {
+                                UploadAndShareFilesWorker.requestStoragePermission(this)
+                            }
                         }
                         .setNegativeButton(R.string.nc_no) {}
                         .show()
@@ -738,6 +741,15 @@ class ChatController(args: Bundle) :
         }
     }
 
+     override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
+        if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+            Log.d(ConversationsListController.TAG, "upload starting after permissions were granted")
+            uploadFiles(filesToUpload)
+        } else {
+            Toast.makeText(context, context?.getString(R.string.read_storage_no_permission), Toast.LENGTH_LONG).show()
+        }
+    }
+
     private fun uploadFiles(files: MutableList<String>) {
         try {
             require(files.isNotEmpty())
@@ -750,6 +762,11 @@ class ChatController(args: Bundle) :
                 .setInputData(data)
                 .build()
             WorkManager.getInstance().enqueue(uploadWorker)
+
+            Toast.makeText(
+                context, context?.getString(R.string.nc_upload_in_progess),
+                Toast.LENGTH_LONG
+            ).show()
         } catch (e: IllegalArgumentException) {
             Toast.makeText(context, context?.resources?.getString(R.string.nc_upload_failed), Toast.LENGTH_LONG).show()
             Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)

+ 233 - 79
app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java

@@ -26,7 +26,10 @@ import android.animation.AnimatorInflater;
 import android.annotation.SuppressLint;
 import android.app.SearchManager;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -42,6 +45,7 @@ import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
+import android.widget.Toast;
 
 import com.bluelinelabs.conductor.RouterTransaction;
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
@@ -54,6 +58,7 @@ import com.facebook.imagepipeline.core.ImagePipeline;
 import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
 import com.facebook.imagepipeline.image.CloseableImage;
 import com.facebook.imagepipeline.request.ImageRequest;
+import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.button.MaterialButton;
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import com.kennyc.bottomsheet.BottomSheet;
@@ -73,6 +78,7 @@ import com.nextcloud.talk.interfaces.ConversationMenuInterface;
 import com.nextcloud.talk.jobs.AccountRemovalWorker;
 import com.nextcloud.talk.jobs.ContactAddressBookWorker;
 import com.nextcloud.talk.jobs.DeleteConversationWorker;
+import com.nextcloud.talk.jobs.UploadAndShareFilesWorker;
 import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.conversations.Conversation;
 import com.nextcloud.talk.models.json.participants.Participant;
@@ -81,6 +87,7 @@ import com.nextcloud.talk.utils.ApiUtils;
 import com.nextcloud.talk.utils.ConductorRemapping;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.KeyboardUtils;
+import com.nextcloud.talk.utils.UriUtils;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
@@ -91,11 +98,13 @@ import org.apache.commons.lang3.builder.CompareToBuilder;
 import org.greenrobot.eventbus.EventBus;
 import org.greenrobot.eventbus.Subscribe;
 import org.greenrobot.eventbus.ThreadMode;
+import org.jetbrains.annotations.NotNull;
 import org.parceler.Parcels;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 import javax.inject.Inject;
 
@@ -128,7 +137,7 @@ public class ConversationsListController extends BaseController implements Searc
         FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FastScroller
                 .OnScrollStateChangeListener, ConversationMenuInterface {
 
-    public static final String TAG = "ConversationsListController";
+    public static final String TAG = "ConvListController";
     public static final int ID_DELETE_CONVERSATION_DIALOG = 0;
     private static final String KEY_SEARCH_QUERY = "ContactsController.searchQuery";
     @Inject
@@ -187,6 +196,12 @@ public class ConversationsListController extends BaseController implements Searc
 
     private Bundle conversationMenuBundle = null;
 
+    private boolean showShareToScreen = false;
+    private boolean shareToScreenWasShown = false;
+
+    private ArrayList<String> filesToShare;
+    private Conversation selectedConversation;
+
     public ConversationsListController() {
         super();
         setHasOptionsMenu(true);
@@ -262,7 +277,6 @@ public class ConversationsListController extends BaseController implements Searc
         if (!eventBus.isRegistered(this)) {
             eventBus.register(this);
         }
-
         currentUser = userUtils.getCurrentUser();
 
         if (currentUser != null) {
@@ -322,73 +336,88 @@ public class ConversationsListController extends BaseController implements Searc
 
         searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
 
-        MainActivity activity = (MainActivity) getActivity();
+        showShareToScreen = !shareToScreenWasShown && hasActivityActionSendIntent();
 
-        searchItem.setVisible(callItems.size() > 0);
-        if (adapter.hasFilter()) {
-            showSearchView(activity, searchView, searchItem);
-            searchView.setQuery(adapter.getFilter(String.class), false);
-        }
-
-        if (activity != null) {
-            activity.binding.searchText.setOnClickListener(v -> {
-                showSearchView(activity, searchView, searchItem);
-                if (getResources() != null) {
-                    DisplayUtils.applyColorToStatusBar(
-                            activity,
-                            ResourcesCompat.getColor(getResources(), R.color.appbar, null)
-                                                      );
-                }
-            });
-        }
+        if (showShareToScreen) {
+            hideSearchBar();
+            getActionBar().setTitle(R.string.send_to_three_dots);
+        } else {
+            MainActivity activity = (MainActivity) getActivity();
 
-        searchView.setOnCloseListener(() -> {
-            if (TextUtils.isEmpty(searchView.getQuery().toString())) {
-                searchView.onActionViewCollapsed();
-                if (activity != null && getResources() != null) {
-                    DisplayUtils.applyColorToStatusBar(
-                            activity,
-                            ResourcesCompat.getColor(getResources(), R.color.bg_default, null)
-                                                      );
+            searchItem.setVisible(callItems.size() > 0);
+            if (activity != null) {
+                if (adapter.hasFilter()) {
+                    showSearchView(activity, searchView, searchItem);
+                    searchView.setQuery(adapter.getFilter(String.class), false);
                 }
-            } else {
-                searchView.post(() -> searchView.setQuery("", true));
-            }
-            return true;
-        });
 
-        searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
-            @Override
-            public boolean onMenuItemActionExpand(MenuItem item) {
-                return true;
+                activity.binding.searchText.setOnClickListener(v -> {
+                    showSearchView(activity, searchView, searchItem);
+                    if (getResources() != null) {
+                        DisplayUtils.applyColorToStatusBar(
+                                activity,
+                                ResourcesCompat.getColor(getResources(), R.color.appbar, null)
+                                                          );
+                    }
+                });
             }
 
-            @Override
-            public boolean onMenuItemActionCollapse(MenuItem item) {
-                searchView.onActionViewCollapsed();
-                MainActivity activity = (MainActivity) getActivity();
-                if (activity != null) {
-                    activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
-                            activity.binding.appBar.getContext(),
-                            R.animator.appbar_elevation_off)
-                                                        );
-                    activity.binding.toolbar.setVisibility(View.GONE);
-                    activity.binding.searchToolbar.setVisibility(View.VISIBLE);
-                    if (getResources() != null) {
+            searchView.setOnCloseListener(() -> {
+                if (TextUtils.isEmpty(searchView.getQuery().toString())) {
+                    searchView.onActionViewCollapsed();
+                    if (activity != null && getResources() != null) {
                         DisplayUtils.applyColorToStatusBar(
                                 activity,
                                 ResourcesCompat.getColor(getResources(), R.color.bg_default, null)
                                                           );
                     }
-                }
-                SmoothScrollLinearLayoutManager layoutManager =
-                        (SmoothScrollLinearLayoutManager) recyclerView.getLayoutManager();
-                if (layoutManager != null) {
-                    layoutManager.scrollToPositionWithOffset(0, 0);
+                } else {
+                    searchView.post(() -> searchView.setQuery(TAG, true));
                 }
                 return true;
-            }
-        });
+            });
+
+            searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+                @Override
+                public boolean onMenuItemActionExpand(MenuItem item) {
+                    return true;
+                }
+
+                @Override
+                public boolean onMenuItemActionCollapse(MenuItem item) {
+                    searchView.onActionViewCollapsed();
+                    MainActivity activity = (MainActivity) getActivity();
+                    if (activity != null) {
+                        activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
+                                activity.binding.appBar.getContext(),
+                                R.animator.appbar_elevation_off)
+                                                            );
+                        activity.binding.toolbar.setVisibility(View.GONE);
+                        activity.binding.searchToolbar.setVisibility(View.VISIBLE);
+                        if (getResources() != null) {
+                            DisplayUtils.applyColorToStatusBar(
+                                    activity,
+                                    ResourcesCompat.getColor(getResources(), R.color.bg_default, null)
+                                                              );
+                        }
+                    }
+                    SmoothScrollLinearLayoutManager layoutManager =
+                            (SmoothScrollLinearLayoutManager) recyclerView.getLayoutManager();
+                    if (layoutManager != null) {
+                        layoutManager.scrollToPositionWithOffset(0, 0);
+                    }
+                    return true;
+                }
+            });
+        }
+    }
+
+    private boolean hasActivityActionSendIntent() {
+        if (getActivity() != null) {
+            return Intent.ACTION_SEND.equals(getActivity().getIntent().getAction())
+                    || Intent.ACTION_SEND_MULTIPLE.equals(getActivity().getIntent().getAction());
+        }
+        return false;
     }
 
     protected void showSearchOrToolbar() {
@@ -414,7 +443,7 @@ public class ConversationsListController extends BaseController implements Searc
 
         callItems = new ArrayList<>();
 
-        int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, ApiUtils.APIv3, 1});
+        int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[]{ApiUtils.APIv4, ApiUtils.APIv3, 1});
 
         roomsQueryDisposable = ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion,
                                                                                    currentUser.getBaseUrl()))
@@ -716,39 +745,70 @@ public class ConversationsListController extends BaseController implements Searc
 
     @Override
     public boolean onItemClick(View view, int position) {
-        Object clickedItem = adapter.getItem(position);
-        if (clickedItem != null && getActivity() != null) {
-            Conversation conversation;
-            if (shouldUseLastMessageLayout) {
-                conversation = ((ConversationItem) clickedItem).getModel();
+        selectedConversation = getConversation(position);
+        if (selectedConversation != null && getActivity() != null) {
+            if (showShareToScreen) {
+                shareToScreenWasShown = true;
+                showShareToConfirmDialog();
             } else {
-                conversation = ((CallItem) clickedItem).getModel();
+                openConversation();
             }
+        }
+        return true;
+    }
 
-            Bundle bundle = new Bundle();
-            bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
-            bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), conversation.getToken());
-            bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), conversation.getRoomId());
+    private void showShareToConfirmDialog() {
+        if (UploadAndShareFilesWorker.Companion.isStoragePermissionGranted(context)) {
+            collectFilesToShareFromIntent();
 
-            if (conversation.hasPassword && (conversation.participantType.equals(Participant.ParticipantType.GUEST) ||
-                    conversation.participantType.equals(Participant.ParticipantType.USER_FOLLOWING_LINK))) {
-                bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 99);
-                prepareAndShowBottomSheetWithBundle(bundle, false);
-            } else {
-                currentUser = userUtils.getCurrentUser();
+            StringBuilder fileNamesWithLineBreaks = new StringBuilder("\n");
 
-                bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(conversation));
-                ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
-                                                                conversation.getToken(), bundle, false);
+            for (String file : filesToShare) {
+                String filename = UriUtils.Companion.getFileName(Uri.parse(file), context);
+                fileNamesWithLineBreaks.append(filename).append("\n");
             }
-        }
 
-        return true;
+            String confirmationQuestion;
+            if (filesToShare.size() == 1) {
+                confirmationQuestion =
+                        String.format(getResources().getString(R.string.nc_upload_confirm_send_single),
+                                      selectedConversation.getDisplayName());
+            } else {
+                confirmationQuestion =
+                        String.format(getResources().getString(R.string.nc_upload_confirm_send_multiple),
+                                      selectedConversation.getDisplayName());
+            }
+
+            new LovelyStandardDialog(getActivity())
+                    .setPositiveButtonColorRes(R.color.nc_darkGreen)
+                    .setTitle(confirmationQuestion)
+                    .setMessage(fileNamesWithLineBreaks.toString())
+                    .setPositiveButton(R.string.nc_yes, new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            upload();
+                            openConversation();
+                        }
+                    })
+                    .setNegativeButton(R.string.nc_no, new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            Log.d(TAG, "sharing files aborted");
+                        }
+                    })
+                    .show();
+        } else {
+            UploadAndShareFilesWorker.Companion.requestStoragePermission(ConversationsListController.this);
+        }
     }
 
     @Override
     public void onItemLongClick(int position) {
-        if (currentUser.hasSpreedFeatureCapability("last-room-activity")) {
+
+        if (showShareToScreen) {
+            Log.d(TAG, "sharing to multiple rooms not yet implemented. onItemLongClick is ignored.");
+
+        } else if (currentUser.hasSpreedFeatureCapability("last-room-activity")) {
             Object clickedItem = adapter.getItem(position);
             if (clickedItem != null) {
                 Conversation conversation;
@@ -764,6 +824,100 @@ public class ConversationsListController extends BaseController implements Searc
         }
     }
 
+    private void collectFilesToShareFromIntent() {
+        filesToShare = new ArrayList<>();
+        if (getActivity() != null && getActivity().getIntent() != null) {
+            Intent intent = getActivity().getIntent();
+            if (Intent.ACTION_SEND.equals(intent.getAction())
+                    || Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
+                if (intent.getClipData() != null) {
+                    for (int i = 0; i < intent.getClipData().getItemCount(); i++) {
+                        filesToShare.add(intent.getClipData().getItemAt(i).getUri().toString());
+                    }
+                } else {
+                    filesToShare.add(intent.getData().toString());
+                }
+                if (filesToShare.isEmpty()) {
+                    Log.e(TAG, "failed to get files from intent");
+                }
+            }
+        }
+    }
+
+    private void upload() {
+        if (selectedConversation == null) {
+            Log.e(TAG, "not able to upload any files because conversation was null.");
+            return;
+        }
+
+        try {
+            String[] filesToShareArray = new String[filesToShare.size()];
+            filesToShareArray = filesToShare.toArray(filesToShareArray);
+
+            Data data = new Data.Builder()
+                    .putStringArray(UploadAndShareFilesWorker.DEVICE_SOURCEFILES, filesToShareArray)
+                    .putString(UploadAndShareFilesWorker.NC_TARGETPATH, currentUser.getAttachmentFolder())
+                    .putString(UploadAndShareFilesWorker.ROOM_TOKEN, selectedConversation.getToken())
+                    .build();
+            OneTimeWorkRequest uploadWorker = new OneTimeWorkRequest.Builder(UploadAndShareFilesWorker.class)
+                    .setInputData(data)
+                    .build();
+            WorkManager.getInstance().enqueue(uploadWorker);
+
+            Toast.makeText(
+                    context, context.getResources().getString(R.string.nc_upload_in_progess),
+                    Toast.LENGTH_LONG
+                          ).show();
+
+        } catch (IllegalArgumentException e) {
+            Toast.makeText(context, context.getResources().getString(R.string.nc_upload_failed), Toast.LENGTH_LONG).show();
+            Log.e(TAG, "Something went wrong when trying to upload file", e);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+        if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION &&
+                grantResults.length > 0 &&
+                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+            Log.d(TAG, "upload starting after permissions were granted");
+            showShareToConfirmDialog();
+        } else {
+            Toast.makeText(context, context.getString(R.string.read_storage_no_permission), Toast.LENGTH_LONG).show();
+        }
+    }
+
+    private void openConversation() {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY(), currentUser);
+        bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN(), selectedConversation.getToken());
+        bundle.putString(BundleKeys.INSTANCE.getKEY_ROOM_ID(), selectedConversation.getRoomId());
+
+        if (selectedConversation.hasPassword && selectedConversation.participantType ==
+                Participant.ParticipantType.GUEST ||
+                selectedConversation.participantType == Participant.ParticipantType.USER_FOLLOWING_LINK) {
+            bundle.putInt(BundleKeys.INSTANCE.getKEY_OPERATION_CODE(), 99);
+            prepareAndShowBottomSheetWithBundle(bundle, false);
+        } else {
+            currentUser = userUtils.getCurrentUser();
+
+            bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(selectedConversation));
+            ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
+                                                            selectedConversation.getToken(), bundle, false);
+        }
+    }
+
+    private Conversation getConversation(int position) {
+        Object clickedItem = adapter.getItem(position);
+        Conversation conversation;
+        if (shouldUseLastMessageLayout) {
+            conversation = ((ConversationItem) clickedItem).getModel();
+        } else {
+            conversation = ((CallItem) clickedItem).getModel();
+        }
+        return conversation;
+    }
+
     @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
     public void onMessageEvent(EventStatus eventStatus) {
         if (currentUser != null && eventStatus.getUserId() == currentUser.getId()) {

+ 15 - 7
app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.java

@@ -169,13 +169,7 @@ public abstract class BaseController extends ButterKnifeController {
                             R.animator.appbar_elevation_off)
                     );
                 } else {
-                    activity.binding.searchToolbar.setVisibility(View.GONE);
-                    activity.binding.toolbar.setVisibility(View.VISIBLE);
-                    layoutParams.setScrollFlags(0);
-                    activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
-                            activity.binding.appBar.getContext(),
-                            R.animator.appbar_elevation_on)
-                    );
+                    hideSearchBar();
                 }
 
                 activity.binding.searchToolbar.setLayoutParams(layoutParams);
@@ -204,6 +198,20 @@ public abstract class BaseController extends ButterKnifeController {
         }
     }
 
+    protected void hideSearchBar() {
+        MainActivity activity = (MainActivity) getActivity();
+        AppBarLayout.LayoutParams layoutParams =
+                (AppBarLayout.LayoutParams) activity.binding.searchToolbar.getLayoutParams();
+
+        activity.binding.searchToolbar.setVisibility(View.GONE);
+        activity.binding.toolbar.setVisibility(View.VISIBLE);
+        layoutParams.setScrollFlags(0);
+        activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
+                activity.binding.appBar.getContext(),
+                R.animator.appbar_elevation_on)
+                                                    );
+    }
+
     @Override
     protected void onDetach(@NonNull View view) {
         super.onDetach(view);

+ 42 - 0
app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt

@@ -20,15 +20,19 @@
 
 package com.nextcloud.talk.jobs
 
+import android.Manifest
 import android.content.Context
 import android.net.Uri
+import android.os.Build
 import android.util.Log
+import androidx.core.content.PermissionChecker
 import androidx.work.Data
 import androidx.work.OneTimeWorkRequest
 import androidx.work.WorkManager
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import autodagger.AutoInjector
+import com.bluelinelabs.conductor.Controller
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
 import com.nextcloud.talk.models.database.UserEntity
@@ -69,6 +73,15 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
     override fun doWork(): Result {
         NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
 
+        if (!isStoragePermissionGranted(context)) {
+            Log.w(
+                TAG, "Storage permission is not granted. As a developer please make sure you check for permissions " +
+                    "via UploadAndShareFilesWorker.isStoragePermissionGranted() and UploadAndShareFilesWorker" +
+                    ".requestStoragePermission() beforehand. If you already did but end up with this warning, the user " +
+                    "most likely revoked the permission"
+            )
+        }
+
         try {
             val currentUser = userUtils.currentUser
             val sourcefiles = inputData.getStringArray(DEVICE_SOURCEFILES)
@@ -172,8 +185,37 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
 
     companion object {
         const val TAG = "UploadFileWorker"
+        const val REQUEST_PERMISSION = 3123
         const val DEVICE_SOURCEFILES = "DEVICE_SOURCEFILES"
         const val NC_TARGETPATH = "NC_TARGETPATH"
         const val ROOM_TOKEN = "ROOM_TOKEN"
+
+        fun isStoragePermissionGranted(context: Context): Boolean {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                if (PermissionChecker.checkSelfPermission(
+                        context,
+                        Manifest.permission.WRITE_EXTERNAL_STORAGE
+                    ) == PermissionChecker.PERMISSION_GRANTED
+                ) {
+                    Log.d(TAG, "Permission is granted")
+                    return true
+                } else {
+                    Log.d(TAG, "Permission is revoked")
+                    return false
+                }
+            } else { //permission is automatically granted on sdk<23 upon installation
+                Log.d(TAG, "Permission is granted")
+                return true
+            }
+        }
+
+        fun requestStoragePermission(controller: Controller) {
+            controller.requestPermissions(
+                arrayOf(
+                    Manifest.permission.WRITE_EXTERNAL_STORAGE
+                ),
+                REQUEST_PERMISSION
+            )
+        }
     }
 }

+ 20 - 19
app/src/main/java/com/nextcloud/talk/utils/UriUtils.kt

@@ -26,28 +26,29 @@ import android.net.Uri
 import android.provider.OpenableColumns
 import android.util.Log
 
-object UriUtils {
-
-    fun getFileName(uri: Uri, context: Context?): String {
-        var filename: String? = null
-        if (uri.scheme == "content" && context != null) {
-            val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
-            try {
-                if (cursor != null && cursor.moveToFirst()) {
-                    filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+class UriUtils {
+    companion object {
+        fun getFileName(uri: Uri, context: Context?): String {
+            var filename: String? = null
+            if (uri.scheme == "content" && context != null) {
+                val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
+                try {
+                    if (cursor != null && cursor.moveToFirst()) {
+                        filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+                    }
+                } finally {
+                    cursor?.close()
                 }
-            } finally {
-                cursor?.close()
             }
-        }
-        if (filename == null) {
-            Log.e("UriUtils", "failed to get DISPLAY_NAME from uri. using fallback.")
-            filename = uri.path
-            val lastIndexOfSlash = filename!!.lastIndexOf('/')
-            if (lastIndexOfSlash != -1) {
-                filename = filename.substring(lastIndexOfSlash + 1)
+            if (filename == null) {
+                Log.d("UriUtils", "failed to get DISPLAY_NAME from uri. using fallback.")
+                filename = uri.path
+                val lastIndexOfSlash = filename!!.lastIndexOf('/')
+                if (lastIndexOfSlash != -1) {
+                    filename = filename.substring(lastIndexOfSlash + 1)
+                }
             }
+            return filename
         }
-        return filename
     }
 }

+ 3 - 0
app/src/main/res/values/strings.xml

@@ -362,6 +362,8 @@
 
     <string name="share">Share</string>
     <string name="send_to">Send to</string>
+    <string name="send_to_three_dots">Send to …</string>
+    <string name="read_storage_no_permission">Sharing files from storage is not possible without permissions</string>
     <string name="open_in_files_app">Open in Files app</string>
 
     <!-- Upload -->
@@ -425,6 +427,7 @@
 
     <!-- Non-translatable strings -->
     <string name="tooManyUnreadMessages" translatable="false">999+</string>
+
     <string name="nc_action_open_main_menu">Open main menu</string>
     <string name="failed_to_save">Failed to save %1$s</string>
     <string name="selected_list_item">selected</string>