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

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2021-05-15 09:39:02 +02:00
parent 74f6f91dc9
commit 36fdd6d82b
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
8 changed files with 358 additions and 122 deletions

View File

@ -63,6 +63,7 @@
android:name="android.permission.USE_CREDENTIALS" android:name="android.permission.USE_CREDENTIALS"
android:maxSdkVersion="22" /> android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <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.WAKE_LOCK" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
@ -96,6 +97,18 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </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> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />

View File

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

View File

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

View File

@ -26,7 +26,10 @@ import android.animation.AnimatorInflater;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -42,6 +45,7 @@ import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.Toast;
import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; 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.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest; 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.button.MaterialButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.kennyc.bottomsheet.BottomSheet; 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.AccountRemovalWorker;
import com.nextcloud.talk.jobs.ContactAddressBookWorker; import com.nextcloud.talk.jobs.ContactAddressBookWorker;
import com.nextcloud.talk.jobs.DeleteConversationWorker; import com.nextcloud.talk.jobs.DeleteConversationWorker;
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker;
import com.nextcloud.talk.models.database.UserEntity; import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.conversations.Conversation; import com.nextcloud.talk.models.json.conversations.Conversation;
import com.nextcloud.talk.models.json.participants.Participant; 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.ConductorRemapping;
import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.KeyboardUtils; import com.nextcloud.talk.utils.KeyboardUtils;
import com.nextcloud.talk.utils.UriUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils; import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences; 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.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.jetbrains.annotations.NotNull;
import org.parceler.Parcels; import org.parceler.Parcels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import javax.inject.Inject; import javax.inject.Inject;
@ -128,7 +137,7 @@ public class ConversationsListController extends BaseController implements Searc
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FastScroller FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemLongClickListener, FastScroller
.OnScrollStateChangeListener, ConversationMenuInterface { .OnScrollStateChangeListener, ConversationMenuInterface {
public static final String TAG = "ConversationsListController"; public static final String TAG = "ConvListController";
public static final int ID_DELETE_CONVERSATION_DIALOG = 0; public static final int ID_DELETE_CONVERSATION_DIALOG = 0;
private static final String KEY_SEARCH_QUERY = "ContactsController.searchQuery"; private static final String KEY_SEARCH_QUERY = "ContactsController.searchQuery";
@Inject @Inject
@ -187,6 +196,12 @@ public class ConversationsListController extends BaseController implements Searc
private Bundle conversationMenuBundle = null; private Bundle conversationMenuBundle = null;
private boolean showShareToScreen = false;
private boolean shareToScreenWasShown = false;
private ArrayList<String> filesToShare;
private Conversation selectedConversation;
public ConversationsListController() { public ConversationsListController() {
super(); super();
setHasOptionsMenu(true); setHasOptionsMenu(true);
@ -262,7 +277,6 @@ public class ConversationsListController extends BaseController implements Searc
if (!eventBus.isRegistered(this)) { if (!eventBus.isRegistered(this)) {
eventBus.register(this); eventBus.register(this);
} }
currentUser = userUtils.getCurrentUser(); currentUser = userUtils.getCurrentUser();
if (currentUser != null) { if (currentUser != null) {
@ -322,73 +336,88 @@ public class ConversationsListController extends BaseController implements Searc
searchView = (SearchView) MenuItemCompat.getActionView(searchItem); searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
MainActivity activity = (MainActivity) getActivity(); showShareToScreen = !shareToScreenWasShown && hasActivityActionSendIntent();
searchItem.setVisible(callItems.size() > 0); if (showShareToScreen) {
if (adapter.hasFilter()) { hideSearchBar();
showSearchView(activity, searchView, searchItem); getActionBar().setTitle(R.string.send_to_three_dots);
searchView.setQuery(adapter.getFilter(String.class), false); } else {
} MainActivity activity = (MainActivity) getActivity();
if (activity != null) { searchItem.setVisible(callItems.size() > 0);
activity.binding.searchText.setOnClickListener(v -> { if (activity != null) {
showSearchView(activity, searchView, searchItem); if (adapter.hasFilter()) {
if (getResources() != null) { showSearchView(activity, searchView, searchItem);
DisplayUtils.applyColorToStatusBar( searchView.setQuery(adapter.getFilter(String.class), false);
activity,
ResourcesCompat.getColor(getResources(), R.color.appbar, null)
);
} }
});
}
searchView.setOnCloseListener(() -> { activity.binding.searchText.setOnClickListener(v -> {
if (TextUtils.isEmpty(searchView.getQuery().toString())) { showSearchView(activity, searchView, searchItem);
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);
if (getResources() != null) { 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( DisplayUtils.applyColorToStatusBar(
activity, activity,
ResourcesCompat.getColor(getResources(), R.color.bg_default, null) ResourcesCompat.getColor(getResources(), R.color.bg_default, null)
); );
} }
} } else {
SmoothScrollLinearLayoutManager layoutManager = searchView.post(() -> searchView.setQuery(TAG, true));
(SmoothScrollLinearLayoutManager) recyclerView.getLayoutManager();
if (layoutManager != null) {
layoutManager.scrollToPositionWithOffset(0, 0);
} }
return 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() { protected void showSearchOrToolbar() {
@ -414,7 +443,7 @@ public class ConversationsListController extends BaseController implements Searc
callItems = new ArrayList<>(); 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, roomsQueryDisposable = ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion,
currentUser.getBaseUrl())) currentUser.getBaseUrl()))
@ -716,39 +745,70 @@ public class ConversationsListController extends BaseController implements Searc
@Override @Override
public boolean onItemClick(View view, int position) { public boolean onItemClick(View view, int position) {
Object clickedItem = adapter.getItem(position); selectedConversation = getConversation(position);
if (clickedItem != null && getActivity() != null) { if (selectedConversation != null && getActivity() != null) {
Conversation conversation; if (showShareToScreen) {
if (shouldUseLastMessageLayout) { shareToScreenWasShown = true;
conversation = ((ConversationItem) clickedItem).getModel(); showShareToConfirmDialog();
} else { } else {
conversation = ((CallItem) clickedItem).getModel(); openConversation();
}
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);
} }
} }
return true; return true;
} }
private void showShareToConfirmDialog() {
if (UploadAndShareFilesWorker.Companion.isStoragePermissionGranted(context)) {
collectFilesToShareFromIntent();
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 @Override
public void onItemLongClick(int position) { 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); Object clickedItem = adapter.getItem(position);
if (clickedItem != null) { if (clickedItem != null) {
Conversation conversation; 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) @Subscribe(sticky = true, threadMode = ThreadMode.BACKGROUND)
public void onMessageEvent(EventStatus eventStatus) { public void onMessageEvent(EventStatus eventStatus) {
if (currentUser != null && eventStatus.getUserId() == currentUser.getId()) { if (currentUser != null && eventStatus.getUserId() == currentUser.getId()) {

View File

@ -169,13 +169,7 @@ public abstract class BaseController extends ButterKnifeController {
R.animator.appbar_elevation_off) R.animator.appbar_elevation_off)
); );
} else { } else {
activity.binding.searchToolbar.setVisibility(View.GONE); hideSearchBar();
activity.binding.toolbar.setVisibility(View.VISIBLE);
layoutParams.setScrollFlags(0);
activity.binding.appBar.setStateListAnimator(AnimatorInflater.loadStateListAnimator(
activity.binding.appBar.getContext(),
R.animator.appbar_elevation_on)
);
} }
activity.binding.searchToolbar.setLayoutParams(layoutParams); 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 @Override
protected void onDetach(@NonNull View view) { protected void onDetach(@NonNull View view) {
super.onDetach(view); super.onDetach(view);

View File

@ -20,15 +20,19 @@
package com.nextcloud.talk.jobs package com.nextcloud.talk.jobs
import android.Manifest
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.content.PermissionChecker
import androidx.work.Data import androidx.work.Data
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import autodagger.AutoInjector import autodagger.AutoInjector
import com.bluelinelabs.conductor.Controller
import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.database.UserEntity
@ -69,6 +73,15 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
override fun doWork(): Result { override fun doWork(): Result {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) 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 { try {
val currentUser = userUtils.currentUser val currentUser = userUtils.currentUser
val sourcefiles = inputData.getStringArray(DEVICE_SOURCEFILES) val sourcefiles = inputData.getStringArray(DEVICE_SOURCEFILES)
@ -172,8 +185,37 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
companion object { companion object {
const val TAG = "UploadFileWorker" const val TAG = "UploadFileWorker"
const val REQUEST_PERMISSION = 3123
const val DEVICE_SOURCEFILES = "DEVICE_SOURCEFILES" const val DEVICE_SOURCEFILES = "DEVICE_SOURCEFILES"
const val NC_TARGETPATH = "NC_TARGETPATH" const val NC_TARGETPATH = "NC_TARGETPATH"
const val ROOM_TOKEN = "ROOM_TOKEN" 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
)
}
} }
} }

View File

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

View File

@ -362,6 +362,8 @@
<string name="share">Share</string> <string name="share">Share</string>
<string name="send_to">Send to</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> <string name="open_in_files_app">Open in Files app</string>
<!-- Upload --> <!-- Upload -->
@ -425,6 +427,7 @@
<!-- Non-translatable strings --> <!-- Non-translatable strings -->
<string name="tooManyUnreadMessages" translatable="false">999+</string> <string name="tooManyUnreadMessages" translatable="false">999+</string>
<string name="nc_action_open_main_menu">Open main menu</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="failed_to_save">Failed to save %1$s</string>
<string name="selected_list_item">selected</string> <string name="selected_list_item">selected</string>