diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8ceb5add2..b92c45f31 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -63,6 +63,7 @@
android:name="android.permission.USE_CREDENTIALS"
android:maxSdkVersion="22" />
+
@@ -96,6 +97,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
index 83122dcc9..ab78ce354 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt
+++ b/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(
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
index e854b589e..416e39025 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt
+++ b/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,9 @@ class ChatController(args: Bundle) :
var pastPreconditionFailed = false
var futurePreconditionFailed = false
+ val filesToUpload: MutableList = ArrayList()
+ var sharedText: String
+
init {
setHasOptionsMenu(true)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
@@ -270,6 +274,7 @@ class ChatController(args: Bundle) :
this.conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)
this.roomId = args.getString(BundleKeys.KEY_ROOM_ID, "")
this.roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN, "")
+ this.sharedText = args.getString(BundleKeys.KEY_SHARED_TEXT, "")
if (args.containsKey(BundleKeys.KEY_ACTIVE_CONVERSATION)) {
this.currentConversation =
@@ -547,7 +552,7 @@ class ChatController(args: Bundle) :
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (s.length >= lengthFilter) {
messageInput?.error = String.format(
- Objects.requireNonNull (resources).getString(R.string.nc_limit_hit),
+ Objects.requireNonNull(resources).getString(R.string.nc_limit_hit),
Integer.toString(lengthFilter)
)
} else {
@@ -580,6 +585,7 @@ class ChatController(args: Bundle) :
}
})
+ messageInput?.setText(sharedText)
messageInputView?.setAttachmentsListener {
activity?.let { AttachmentDialog(it, this).show() }
}
@@ -683,27 +689,27 @@ class ChatController(args: Bundle) :
if (resultCode == RESULT_OK) {
try {
checkNotNull(intent)
- val files: MutableList = 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 +723,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 +744,15 @@ class ChatController(args: Bundle) :
}
}
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, 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) {
try {
require(files.isNotEmpty())
@@ -750,6 +765,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)
@@ -1307,9 +1327,9 @@ class ChatController(args: Bundle) :
TextUtils.isEmpty(chatMessageList[i + 1].systemMessage) &&
chatMessageList[i + 1].actorId == chatMessageList[i].actorId &&
countGroupedMessages < 4 && DateFormatter.isSameDay(
- chatMessageList[i].createdAt,
- chatMessageList[i + 1].createdAt
- )
+ chatMessageList[i].createdAt,
+ chatMessageList[i + 1].createdAt
+ )
) {
chatMessageList[i].isGrouped = true
countGroupedMessages++
@@ -1620,7 +1640,8 @@ class ChatController(args: Bundle) :
true
}
R.id.action_reply_privately -> {
- val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
+ val apiVersion =
+ ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.APIv4, 1))
val retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(
apiVersion,
conversationUser?.baseUrl,
@@ -1669,6 +1690,7 @@ class ChatController(args: Bundle) :
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
+
override fun onComplete() {}
})
}
@@ -1676,6 +1698,7 @@ class ChatController(args: Bundle) :
override fun onError(e: Throwable) {
Log.e(TAG, e.message, e)
}
+
override fun onComplete() {}
})
true
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
index d060842e4..744af7aff 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
+++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.java
@@ -25,8 +25,12 @@ package com.nextcloud.talk.controllers;
import android.animation.AnimatorInflater;
import android.annotation.SuppressLint;
import android.app.SearchManager;
+import android.content.ClipData;
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 +46,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;
@@ -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;
@@ -128,7 +135,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 +194,14 @@ public class ConversationsListController extends BaseController implements Searc
private Bundle conversationMenuBundle = null;
+ private boolean showShareToScreen = false;
+ private boolean shareToScreenWasShown = false;
+
+ private ArrayList filesToShare;
+ private Conversation selectedConversation;
+
+ private String textToPaste = "";
+
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 (showShareToScreen) {
+ hideSearchBar();
+ getActionBar().setTitle(R.string.send_to_three_dots);
+ } else {
+ MainActivity activity = (MainActivity) getActivity();
- 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)
- );
+ searchItem.setVisible(callItems.size() > 0);
+ if (activity != null) {
+ if (adapter.hasFilter()) {
+ showSearchView(activity, searchView, searchItem);
+ searchView.setQuery(adapter.getFilter(String.class), false);
}
- });
- }
- 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)
- );
- }
- } else {
- searchView.post(() -> searchView.setQuery("", 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);
+ activity.binding.searchText.setOnClickListener(v -> {
+ showSearchView(activity, searchView, searchItem);
if (getResources() != null) {
+ DisplayUtils.applyColorToStatusBar(
+ activity,
+ ResourcesCompat.getColor(getResources(), R.color.appbar, 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,79 @@ 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;
+ handleSharedData();
} else {
- conversation = ((CallItem) clickedItem).getModel();
- }
-
- 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());
-
- 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();
-
- bundle.putParcelable(BundleKeys.INSTANCE.getKEY_ACTIVE_CONVERSATION(), Parcels.wrap(conversation));
- ConductorRemapping.INSTANCE.remapChatController(getRouter(), currentUser.getId(),
- conversation.getToken(), bundle, false);
+ openConversation();
}
}
-
return true;
}
+ private void handleSharedData() {
+ collectDataFromIntent();
+ if (!textToPaste.isEmpty()) {
+ openConversation(textToPaste);
+ } else if (filesToShare != null && !filesToShare.isEmpty()) {
+ showSendFilesConfirmDialog();
+ } else {
+ Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private void showSendFilesConfirmDialog() {
+ if (UploadAndShareFilesWorker.Companion.isStoragePermissionGranted(context)) {
+ StringBuilder fileNamesWithLineBreaks = new StringBuilder("\n");
+
+ for (String file : filesToShare) {
+ String filename = UriUtils.Companion.getFileName(Uri.parse(file), context);
+ fileNamesWithLineBreaks.append(filename).append("\n");
+ }
+
+ 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 +833,123 @@ public class ConversationsListController extends BaseController implements Searc
}
}
+ private void collectDataFromIntent() {
+ 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())) {
+ try {
+ if (intent.getClipData() != null) {
+ for (int i = 0; i < intent.getClipData().getItemCount(); i++) {
+ ClipData.Item item = intent.getClipData().getItemAt(i);
+ if (item.getUri() != null) {
+ filesToShare.add(intent.getClipData().getItemAt(i).getUri().toString());
+ } else if (item.getText() != null) {
+ textToPaste = intent.getClipData().getItemAt(i).getText().toString();
+ break;
+ } else {
+ Log.w(TAG, "datatype not yet implemented for share-to");
+ }
+ }
+ } else {
+ filesToShare.add(intent.getData().toString());
+ }
+ if (filesToShare.isEmpty() && textToPaste.isEmpty()) {
+ Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
+ Toast.LENGTH_LONG).show();
+ Log.e(TAG, "failed to get data from intent");
+ }
+ } catch (Exception e) {
+ Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
+ Toast.LENGTH_LONG).show();
+ Log.e(TAG, "Something went wrong when extracting data from intent");
+ }
+ }
+ }
+ }
+
+ private void upload() {
+ if (selectedConversation == null) {
+ Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
+ Toast.LENGTH_LONG).show();
+ 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");
+ showSendFilesConfirmDialog();
+ } else {
+ Toast.makeText(context, context.getString(R.string.read_storage_no_permission), Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private void openConversation() {
+ openConversation("");
+ }
+
+ private void openConversation(String textToPaste) {
+ 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());
+ bundle.putString(BundleKeys.INSTANCE.getKEY_SHARED_TEXT(), textToPaste);
+
+ 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()) {
diff --git a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.java b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.java
index 8282bf63a..8353afbfe 100644
--- a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.java
+++ b/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);
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt
index 3b7a12b9a..0bf2b4174 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt
+++ b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt
@@ -20,15 +20,21 @@
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 android.widget.Toast
+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.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity
@@ -69,6 +75,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)
@@ -88,9 +103,13 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
uploadFile(currentUser, ncTargetpath, filename, roomToken, requestBody, sourcefileUri)
}
} catch (e: IllegalStateException) {
+ Toast.makeText(context, context.resources.getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG)
+ .show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
return Result.failure()
} catch (e: IllegalArgumentException) {
+ Toast.makeText(context, context.resources.getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG)
+ .show()
Log.e(javaClass.simpleName, "Something went wrong when trying to upload file", e)
return Result.failure()
}
@@ -105,6 +124,8 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
while (input.read(buf) != -1)
requestBody = RequestBody.create("application/octet-stream".toMediaTypeOrNull(), buf)
} catch (e: Exception) {
+ Toast.makeText(context, context.resources.getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG)
+ .show()
Log.e(javaClass.simpleName, "failed to create RequestBody for $sourcefileUri", e)
}
return requestBody
@@ -133,6 +154,8 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
}
override fun onError(e: Throwable) {
+ Toast.makeText(context, context.resources.getString(R.string.nc_common_error_sorry), Toast.LENGTH_LONG)
+ .show()
Log.e(TAG, "failed to upload file $filename")
}
@@ -172,8 +195,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
+ )
+ }
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/UriUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/UriUtils.kt
index 42dc9eb24..1397e7714 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/UriUtils.kt
+++ b/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
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
index bb8c6c42e..39ea5d998 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
@@ -65,4 +65,5 @@ object BundleKeys {
val KEY_ACCOUNT = "KEY_ACCOUNT"
val KEY_FILE_ID = "KEY_FILE_ID"
val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
+ val KEY_SHARED_TEXT = "KEY_SHARED_TEXT"
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 277c784d6..17c40d437 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -362,6 +362,8 @@
Share
Send to
+ Send to …
+ Sharing files from storage is not possible without permissions
Open in Files app
@@ -425,6 +427,7 @@
999+
+
Open main menu
Failed to save %1$s
selected
diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt
index 7d4983b98..2da432533 100644
--- a/scripts/analysis/findbugs-results.txt
+++ b/scripts/analysis/findbugs-results.txt
@@ -1 +1 @@
-458
\ No newline at end of file
+457
\ No newline at end of file