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