open files from media view

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-04-26 18:09:02 +02:00 committed by Tim Krüger
parent 3a5a3cebfb
commit 912bc3c8fe
No known key found for this signature in database
GPG key ID: FECE3A7222C52A4E
7 changed files with 221 additions and 105 deletions

View file

@ -1,18 +1,18 @@
package com.nextcloud.talk.adapters
import android.net.Uri
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.interfaces.DraweeController
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.common.RotationOptions
import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.nextcloud.talk.activities.SharedItemsActivity
import com.nextcloud.talk.databinding.AttachmentItemBinding
import com.nextcloud.talk.repositories.SharedItem
import com.nextcloud.talk.utils.FileViewerUtils
class SharedItemsAdapter : RecyclerView.Adapter<SharedItemsAdapter.ViewHolder>() {
@ -50,7 +50,19 @@ class SharedItemsAdapter : RecyclerView.Adapter<SharedItemsAdapter.ViewHolder>()
holder.binding.image.controller = draweeController
holder.binding.image.setOnClickListener {
Log.d(TAG, "clicked " + currentItem.name)
val fileViewerUtils = FileViewerUtils(it.context, currentItem.userEntity)
fileViewerUtils.openFile(
currentItem.id,
currentItem.name,
currentItem.fileSize,
currentItem.path,
currentItem.link,
currentItem.mimeType,
null,
null,
it as SimpleDraweeView
)
}
}
}

View file

@ -41,7 +41,6 @@ import android.widget.PopupMenu;
import android.widget.ProgressBar;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.common.util.concurrent.ListenableFuture;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
@ -59,16 +58,14 @@ import com.stfalcon.chatkit.messages.MessageHolders;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.content.ContextCompat;
import androidx.emoji.widget.EmojiTextView;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import autodagger.AutoInjector;
import io.reactivex.Single;
import io.reactivex.SingleObserver;
@ -112,7 +109,6 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
public MagicPreviewMessageViewHolder(View itemView, Object payload) {
super(itemView, payload);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
fileViewerUtils = new FileViewerUtils(context);
}
@SuppressLint("SetTextI18n")
@ -154,6 +150,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
fileViewerUtils = new FileViewerUtils(context, message.activeUser);
String fileName = message.getSelectedIndividualHashMap().get(KEY_NAME);
getMessageText().setText(fileName);
if (message.getSelectedIndividualHashMap().containsKey(KEY_CONTACT_NAME)) {
@ -183,52 +181,26 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
}
if (message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null) {
String accountString =
message.activeUser.getUsername() + "@" +
message.activeUser.getBaseUrl()
.replace("https://", "")
.replace("http://", "");
clickView.setOnClickListener(v -> {
fileViewerUtils.openFile(message, progressBar, getMessageText(), image);
});
clickView.setOnLongClickListener(l -> {
onMessageViewLongClick(message, accountString);
onMessageViewLongClick(message);
return true;
});
} else {
Log.e(TAG, "failed to set click listener because activeUser, username or baseUrl were null");
}
fileViewerUtils.resumeToUpdateViewsByProgress(
Objects.requireNonNull(message.getSelectedIndividualHashMap().get(MagicPreviewMessageViewHolder.KEY_NAME)),
Objects.requireNonNull(message.getSelectedIndividualHashMap().get(MagicPreviewMessageViewHolder.KEY_ID)),
Objects.requireNonNull(message.getSelectedIndividualHashMap().get(MagicPreviewMessageViewHolder.KEY_MIMETYPE)),
progressBar,
getMessageText(),
image);
// check if download worker is already running
String fileId = message.getSelectedIndividualHashMap().get(KEY_ID);
ListenableFuture<List<WorkInfo>> workers = WorkManager.getInstance(context).getWorkInfosByTag(fileId);
try {
for (WorkInfo workInfo : workers.get()) {
if (workInfo.getState() == WorkInfo.State.RUNNING ||
workInfo.getState() == WorkInfo.State.ENQUEUED) {
progressBar.setVisibility(View.VISIBLE);
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
WorkManager
.getInstance(context)
.getWorkInfoByIdLiveData(workInfo.getId())
.observeForever(info -> fileViewerUtils.updateViewsByProgress(
fileName,
mimetype,
info,
progressBar,
getMessageText(),
image));
}
}
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Error when checking if worker already exists", e);
}
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
getMessageText().setText("GIPHY");
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", getMessageText());
@ -279,7 +251,7 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
return drawable;
}
private void onMessageViewLongClick(ChatMessage message, String accountString) {
private void onMessageViewLongClick(ChatMessage message) {
if (fileViewerUtils.isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
previewMessageInterface.onPreviewMessageLongClick(message);
return;
@ -301,7 +273,11 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
popupMenu.inflate(R.menu.chat_preview_message_menu);
popupMenu.setOnMenuItemClickListener(item -> {
fileViewerUtils.openFileInFilesApp(message, accountString);
if (item.getItemId()== R.id.openInFiles){
String keyID = message.getSelectedIndividualHashMap().get(KEY_ID);
String link = message.getSelectedIndividualHashMap().get("link");
fileViewerUtils.openFileInFilesApp(link, keyID);
}
return true;
});

View file

@ -156,6 +156,9 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
if (MessageDigest.isEqual(
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
("file").getBytes(Charsets.UTF_8))) {
// TODO: this selectedIndividualHashMap stuff needs to be analyzed and most likely be refactored!
// it just feels wrong to fill this here inside getImageUrl()
selectedIndividualHashMap = individualHashMap;
if (!isVoiceMessage()) {
if (getActiveUser() != null && getActiveUser().getBaseUrl() != null) {

View file

@ -1,10 +1,15 @@
package com.nextcloud.talk.repositories
import com.nextcloud.talk.models.database.UserEntity
data class SharedItem(
val id: String,
val name: String,
val mimeType: String,
val fileSize: Int,
val path: String,
val link: String,
val mimeType: String,
val previewAvailable: Boolean,
val previewLink: String
val previewLink: String,
val userEntity: UserEntity,
)

View file

@ -4,6 +4,7 @@ import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.chat.ChatShareOverall
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observable
@ -48,6 +49,7 @@ class SharedItemsRepository {
val userName: String,
val userToken: String,
val baseUrl: String,
val userEntity: UserEntity,
val roomToken: String
)
}

View file

@ -27,23 +27,68 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
import androidx.emoji.widget.EmojiTextView
import com.facebook.drawee.view.SimpleDraweeView
import com.nextcloud.talk.models.database.UserEntity
import java.io.File
import java.util.concurrent.ExecutionException
class FileViewerUtils(private val context: Context) {
fun openFile(message: ChatMessage, progressBar: ProgressBar, messageText: EmojiTextView, previewImage: SimpleDraweeView) {
val accountString = message.activeUser.username + "@" +
message.activeUser.baseUrl
.replace("https://", "")
.replace("http://", "")
class FileViewerUtils(private val context: Context, private val userEntity: UserEntity) {
fun openFile(
message: ChatMessage,
progressBar: ProgressBar?,
messageText: EmojiTextView?,
previewImage: SimpleDraweeView
) {
val fileName = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_NAME]!!
val mimetype = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_MIMETYPE]!!
val link = message.getSelectedIndividualHashMap()["link"]!!
val fileId = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_ID]!!
val path = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_PATH]!!
var size = message.getSelectedIndividualHashMap()["size"]
if (size == null) {
size = "-1"
}
val fileSize = Integer.valueOf(size)
openFile(
fileId,
fileName,
fileSize,
path,
link,
mimetype,
progressBar,
messageText,
previewImage
)
}
fun openFile(
fileId: String,
fileName: String,
fileSize: Int,
path: String,
link: String,
mimetype: String,
progressBar: ProgressBar?,
messageText: EmojiTextView?,
previewImage: SimpleDraweeView
) {
if (isSupportedForInternalViewer(mimetype) || canBeHandledByExternalApp(mimetype, fileName)) {
openOrDownloadFile(message, progressBar, messageText, previewImage)
openOrDownloadFile(
fileName,
fileId,
path,
fileSize,
mimetype,
progressBar,
messageText,
previewImage
)
} else {
openFileInFilesApp(message, accountString)
openFileInFilesApp(link, fileId)
}
}
@ -55,26 +100,51 @@ class FileViewerUtils(private val context: Context) {
return intent.resolveActivity(context.packageManager) != null
}
private fun openOrDownloadFile(message: ChatMessage, progressBar: ProgressBar, messageText: EmojiTextView, previewImage: SimpleDraweeView) {
val filename = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_NAME]
val mimetype = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_MIMETYPE]
val file = File(context.cacheDir, filename)
private fun openOrDownloadFile(
fileName: String,
fileId: String,
path: String,
fileSize: Int,
mimetype: String,
progressBar: ProgressBar?,
messageText: EmojiTextView?,
previewImage: SimpleDraweeView
) {
val file = File(context.cacheDir, fileName)
if (file.exists()) {
openFile(filename!!, mimetype!!)
openFileByMimetype(fileName!!, mimetype!!)
} else {
downloadFileToCache(message, progressBar, messageText, previewImage)
downloadFileToCache(
fileName,
fileId,
path,
fileSize,
mimetype,
progressBar,
messageText,
previewImage
)
}
}
private fun openFile(filename: String, mimetype: String) {
private fun openFileByMimetype(filename: String, mimetype: String) {
when (mimetype) {
"audio/mpeg", "audio/wav", "audio/ogg", "video/mp4", "video/quicktime", "video/ogg" -> openMediaView(
filename,
mimetype
)
"image/png", "image/jpeg", "image/gif" -> openImageView(filename, mimetype)
"text/markdown", "text/plain" -> openTextView(filename, mimetype)
else -> openFileByExternalApp(filename, mimetype)
"audio/mpeg",
"audio/wav",
"audio/ogg",
"video/mp4",
"video/quicktime",
"video/ogg"
-> openMediaView(filename, mimetype)
"image/png",
"image/jpeg",
"image/gif"
-> openImageView(filename, mimetype)
"text/markdown",
"text/plain"
-> openTextView(filename, mimetype)
else
-> openFileByExternalApp(filename, mimetype)
}
}
@ -106,7 +176,12 @@ class FileViewerUtils(private val context: Context) {
}
}
fun openFileInFilesApp(message: ChatMessage, accountString: String) {
fun openFileInFilesApp(link: String, keyID: String) {
val accountString = userEntity.username + "@" +
userEntity.baseUrl
.replace("https://", "")
.replace("http://", "")
if (canWeOpenFilesApp(context, accountString)) {
val filesAppIntent = Intent(Intent.ACTION_VIEW, null)
val componentName = ComponentName(
@ -117,15 +192,12 @@ class FileViewerUtils(private val context: Context) {
filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
filesAppIntent.setPackage(context.getString(R.string.nc_import_accounts_from))
filesAppIntent.putExtra(KEY_ACCOUNT, accountString)
filesAppIntent.putExtra(
KEY_FILE_ID,
message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_ID]
)
filesAppIntent.putExtra(KEY_FILE_ID, keyID)
context.startActivity(filesAppIntent)
} else {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse(message.getSelectedIndividualHashMap()["link"])
Uri.parse(link)
)
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(browserIntent)
@ -176,20 +248,16 @@ class FileViewerUtils(private val context: Context) {
}
@SuppressLint("LongLogTag")
private fun downloadFileToCache(message: ChatMessage, progressBar: ProgressBar, messageText: EmojiTextView, previewImage: SimpleDraweeView) {
val baseUrl = message.activeUser.baseUrl
val userId = message.activeUser.userId
val attachmentFolder = CapabilitiesUtil.getAttachmentFolder(message.activeUser)
val fileName = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_NAME]!!
val mimetype = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_MIMETYPE]!!
var size = message.getSelectedIndividualHashMap()["size"]
if (size == null) {
size = "-1"
}
val fileSize = Integer.valueOf(size)
val fileId = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_ID]
val path = message.getSelectedIndividualHashMap()[MagicPreviewMessageViewHolder.KEY_PATH]
private fun downloadFileToCache(
fileName: String,
fileId: String,
path: String,
fileSize: Int,
mimetype: String,
progressBar: ProgressBar?,
messageText: EmojiTextView?,
previewImage: SimpleDraweeView
) {
// check if download worker is already running
val workers = WorkManager.getInstance(context).getWorkInfosByTag(
fileId!!
@ -208,9 +276,12 @@ class FileViewerUtils(private val context: Context) {
}
val downloadWorker: OneTimeWorkRequest
val data: Data = Data.Builder()
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, baseUrl)
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userId)
.putString(DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER, attachmentFolder)
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, userEntity.baseUrl)
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userEntity.userId)
.putString(
DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER,
CapabilitiesUtil.getAttachmentFolder(userEntity)
)
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileName)
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
.putInt(DownloadFileToCacheWorker.KEY_FILE_SIZE, fileSize)
@ -220,7 +291,7 @@ class FileViewerUtils(private val context: Context) {
.addTag(fileId)
.build()
WorkManager.getInstance().enqueue(downloadWorker)
progressBar.visibility = View.VISIBLE
progressBar?.visibility = View.VISIBLE
WorkManager.getInstance(context).getWorkInfoByIdLiveData(downloadWorker.id)
.observeForever { workInfo: WorkInfo? ->
updateViewsByProgress(
@ -234,19 +305,19 @@ class FileViewerUtils(private val context: Context) {
}
}
fun updateViewsByProgress(
private fun updateViewsByProgress(
fileName: String,
mimetype: String,
workInfo: WorkInfo,
progressBar: ProgressBar,
messageText: EmojiTextView,
progressBar: ProgressBar?,
messageText: EmojiTextView?,
previewImage: SimpleDraweeView
) {
when (workInfo.state) {
WorkInfo.State.RUNNING -> {
val progress = workInfo.progress.getInt(DownloadFileToCacheWorker.PROGRESS, -1)
if (progress > -1) {
messageText.text = String.format(
messageText?.text = String.format(
context.resources.getString(R.string.filename_progress),
fileName,
progress
@ -255,25 +326,66 @@ class FileViewerUtils(private val context: Context) {
}
WorkInfo.State.SUCCEEDED -> {
if (previewImage.isShown) {
openFile(fileName, mimetype)
openFileByMimetype(fileName, mimetype)
} else {
Log.d(TAG, "file " + fileName +
Log.d(
TAG, "file " + fileName +
" was downloaded but it's not opened because view is not shown on screen"
)
}
messageText.text = fileName
progressBar.visibility = View.GONE
messageText?.text = fileName
progressBar?.visibility = View.GONE
}
WorkInfo.State.FAILED -> {
messageText.text = fileName
progressBar.visibility = View.GONE
messageText?.text = fileName
progressBar?.visibility = View.GONE
}
else -> {
}
}
}
fun resumeToUpdateViewsByProgress(
fileName: String,
fileId: String,
mimeType: String,
progressBar: ProgressBar,
messageText: EmojiTextView?,
previewImage: SimpleDraweeView
) {
val workers = WorkManager.getInstance(context).getWorkInfosByTag(fileId)
try {
for (workInfo in workers.get()) {
if (workInfo.state == WorkInfo.State.RUNNING ||
workInfo.state == WorkInfo.State.ENQUEUED
) {
progressBar.visibility = View.VISIBLE
WorkManager
.getInstance(context)
.getWorkInfoByIdLiveData(workInfo.id)
.observeForever { info: WorkInfo? ->
updateViewsByProgress(
fileName,
mimeType,
info!!,
progressBar,
messageText,
previewImage
)
}
}
}
} catch (e: ExecutionException) {
Log.e(TAG, "Error when checking if worker already exists", e)
} catch (e: InterruptedException) {
Log.e(TAG, "Error when checking if worker already exists", e)
}
}
companion object {
private val TAG = FileViewerUtils::class.simpleName
const val KEY_ID = "id"
}
}

View file

@ -51,10 +51,15 @@ class SharedItemsViewModel(private val repository: SharedItemsRepository) : View
val previewAvailable = "yes".equals(fileParameters["preview-available"]!!, ignoreCase = true)
items[it.value.id] = SharedItem(
fileParameters["id"]!!, fileParameters["name"]!!,
fileParameters["mimetype"]!!, fileParameters["link"]!!,
fileParameters["id"]!!,
fileParameters["name"]!!,
fileParameters["size"]!!.toInt(),
fileParameters["path"]!!,
fileParameters["link"]!!,
fileParameters["mimetype"]!!,
previewAvailable,
repository.previewLink(fileParameters["id"])
repository.previewLink(fileParameters["id"]),
repository.parameters!!.userEntity
)
}
}
@ -84,6 +89,7 @@ class SharedItemsViewModel(private val repository: SharedItemsRepository) : View
userEntity.userId,
userEntity.token,
userEntity.baseUrl,
userEntity,
roomToken
)