mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-27 08:55:54 +03:00
Merge commit 'f5d70dab0c09435c77346415b09f580ebfa30102'
This commit is contained in:
commit
b9be4fc195
27 changed files with 1562 additions and 370 deletions
|
@ -332,6 +332,10 @@ dependencies {
|
|||
|
||||
gplayImplementation 'com.google.android.gms:play-services-base:18.0.1'
|
||||
gplayImplementation "com.google.firebase:firebase-messaging:23.0.0"
|
||||
|
||||
// TODO: Define variable for version
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
|
||||
// implementation 'androidx.activity:activity-ktx:1.4.0'
|
||||
}
|
||||
|
||||
task installGitHooks(type: Copy, group: "development") {
|
||||
|
|
|
@ -96,6 +96,11 @@
|
|||
android:name="android.max_aspect"
|
||||
android:value="10" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.SharedItemsActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/AppTheme"/>
|
||||
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:label="@string/nc_app_name"
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
package com.nextcloud.talk.activities
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.SharedItemsAdapter
|
||||
import com.nextcloud.talk.adapters.SharedItemsListAdapter
|
||||
import com.nextcloud.talk.databinding.ActivitySharedItemsBinding
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
|
||||
import com.nextcloud.talk.viewmodels.SharedItemsViewModel
|
||||
|
||||
class SharedItemsActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivitySharedItemsBinding
|
||||
private lateinit var viewModel: SharedItemsViewModel
|
||||
private lateinit var currentTab: String
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
currentTab = TAB_MEDIA
|
||||
|
||||
val roomToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
|
||||
val conversationName = intent.getStringExtra(KEY_CONVERSATION_NAME)
|
||||
val userEntity = intent.getParcelableExtra<UserEntity>(KEY_USER_ENTITY)!!
|
||||
|
||||
binding = ActivitySharedItemsBinding.inflate(layoutInflater)
|
||||
setSupportActionBar(binding.sharedItemsToolbar)
|
||||
setContentView(binding.root)
|
||||
|
||||
DisplayUtils.applyColorToStatusBar(
|
||||
this,
|
||||
ResourcesCompat.getColor(
|
||||
resources, R.color.appbar, null
|
||||
)
|
||||
)
|
||||
DisplayUtils.applyColorToNavigationBar(
|
||||
this.window,
|
||||
ResourcesCompat.getColor(resources, R.color.bg_default, null)
|
||||
)
|
||||
|
||||
supportActionBar?.title = conversationName
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
initTabs()
|
||||
|
||||
viewModel = ViewModelProvider(
|
||||
this,
|
||||
SharedItemsViewModel.Factory(userEntity, roomToken, currentTab)
|
||||
).get(SharedItemsViewModel::class.java)
|
||||
|
||||
viewModel.sharedItems.observe(this) {
|
||||
Log.d(TAG, "Items received: $it")
|
||||
|
||||
if (currentTab == TAB_MEDIA) {
|
||||
val adapter = SharedItemsAdapter()
|
||||
adapter.items = it.items
|
||||
adapter.authHeader = it.authHeader
|
||||
binding.imageRecycler.adapter = adapter
|
||||
|
||||
val layoutManager = GridLayoutManager(this, 4)
|
||||
binding.imageRecycler.layoutManager = layoutManager
|
||||
} else {
|
||||
val adapter = SharedItemsListAdapter()
|
||||
adapter.items = it.items
|
||||
adapter.authHeader = it.authHeader
|
||||
binding.imageRecycler.adapter = adapter
|
||||
|
||||
val layoutManager = LinearLayoutManager(this)
|
||||
layoutManager.orientation = LinearLayoutManager.VERTICAL
|
||||
binding.imageRecycler.layoutManager = layoutManager
|
||||
}
|
||||
}
|
||||
|
||||
binding.imageRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
if (!recyclerView.canScrollVertically(1) && newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
viewModel.loadNextItems()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun updateItems(type: String) {
|
||||
currentTab = type
|
||||
viewModel.loadItems(type)
|
||||
}
|
||||
|
||||
private fun initTabs() {
|
||||
val tabMedia: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
tabMedia.tag = TAB_MEDIA
|
||||
tabMedia.setText(R.string.shared_items_media)
|
||||
binding.sharedItemsTabs.addTab(tabMedia)
|
||||
|
||||
val tabFile: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
tabFile.tag = TAB_FILE
|
||||
tabFile.setText(R.string.shared_items_file)
|
||||
binding.sharedItemsTabs.addTab(tabFile)
|
||||
|
||||
val tabAudio: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
tabAudio.tag = TAB_AUDIO
|
||||
tabAudio.setText(R.string.shared_items_audio)
|
||||
binding.sharedItemsTabs.addTab(tabAudio)
|
||||
|
||||
val tabVoice: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
tabVoice.tag = TAB_VOICE
|
||||
tabVoice.setText(R.string.shared_items_voice)
|
||||
binding.sharedItemsTabs.addTab(tabVoice)
|
||||
|
||||
// val tabLocation: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
// tabLocation.tag = TAB_LOCATION
|
||||
// tabLocation.text = "location"
|
||||
// binding.sharedItemsTabs.addTab(tabLocation)
|
||||
|
||||
// val tabDeckCard: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
// tabDeckCard.tag = TAB_DECKCARD
|
||||
// tabDeckCard.text = "deckcard"
|
||||
// binding.sharedItemsTabs.addTab(tabDeckCard)
|
||||
|
||||
val tabOther: TabLayout.Tab = binding.sharedItemsTabs.newTab()
|
||||
tabOther.tag = TAB_OTHER
|
||||
tabOther.setText(R.string.shared_items_other)
|
||||
binding.sharedItemsTabs.addTab(tabOther)
|
||||
|
||||
binding.sharedItemsTabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
updateItems(tab.tag as String)
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
|
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) = Unit
|
||||
})
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == android.R.id.home) {
|
||||
onBackPressed()
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = SharedItemsActivity::class.simpleName
|
||||
const val TAB_AUDIO = "audio"
|
||||
const val TAB_FILE = "file"
|
||||
const val TAB_MEDIA = "media"
|
||||
const val TAB_VOICE = "voice"
|
||||
const val TAB_LOCATION = "location"
|
||||
const val TAB_DECKCARD = "deckcard"
|
||||
const val TAB_OTHER = "other"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package com.nextcloud.talk.adapters
|
||||
|
||||
import android.net.Uri
|
||||
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.R
|
||||
import com.nextcloud.talk.databinding.AttachmentItemBinding
|
||||
import com.nextcloud.talk.repositories.SharedItem
|
||||
import com.nextcloud.talk.utils.FileViewerUtils
|
||||
|
||||
class SharedItemsAdapter : RecyclerView.Adapter<SharedItemsAdapter.ViewHolder>() {
|
||||
|
||||
companion object {
|
||||
private val TAG = SharedItemsAdapter::class.simpleName
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: AttachmentItemBinding, itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
var authHeader: Map<String, String> = emptyMap()
|
||||
var items: List<SharedItem> = emptyList()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = AttachmentItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding, binding.root)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
||||
val currentItem = items[position]
|
||||
|
||||
if (currentItem.previewAvailable) {
|
||||
val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(currentItem.previewLink))
|
||||
.setProgressiveRenderingEnabled(true)
|
||||
.setRotationOptions(RotationOptions.autoRotate())
|
||||
.disableDiskCache()
|
||||
.setHeaders(authHeader)
|
||||
.build()
|
||||
|
||||
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setOldController(holder.binding.image.controller)
|
||||
.setAutoPlayAnimations(true)
|
||||
.setImageRequest(imageRequest)
|
||||
.build()
|
||||
holder.binding.image.controller = draweeController
|
||||
} else {
|
||||
when (currentItem.mimeType) {
|
||||
"video/mp4",
|
||||
"video/quicktime",
|
||||
"video/ogg"
|
||||
-> holder.binding.image.setImageResource(R.drawable.ic_mimetype_video)
|
||||
"audio/mpeg",
|
||||
"audio/wav",
|
||||
"audio/ogg",
|
||||
-> holder.binding.image.setImageResource(R.drawable.ic_mimetype_audio)
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif"
|
||||
-> holder.binding.image.setImageResource(R.drawable.ic_mimetype_image)
|
||||
"text/markdown",
|
||||
"text/plain"
|
||||
-> holder.binding.image.setImageResource(R.drawable.ic_mimetype_text)
|
||||
else
|
||||
-> holder.binding.image.setImageResource(R.drawable.ic_mimetype_file)
|
||||
}
|
||||
}
|
||||
holder.binding.image.setOnClickListener {
|
||||
val fileViewerUtils = FileViewerUtils(it.context, currentItem.userEntity)
|
||||
|
||||
fileViewerUtils.openFile(
|
||||
currentItem.id,
|
||||
currentItem.name,
|
||||
currentItem.fileSize,
|
||||
currentItem.path,
|
||||
currentItem.link,
|
||||
currentItem.mimeType,
|
||||
holder.binding.progressBar,
|
||||
null,
|
||||
it as SimpleDraweeView
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package com.nextcloud.talk.adapters
|
||||
|
||||
import android.net.Uri
|
||||
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.imagepipeline.common.RotationOptions
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.databinding.AttachmentListItemBinding
|
||||
import com.nextcloud.talk.repositories.SharedItem
|
||||
import com.nextcloud.talk.utils.FileViewerUtils
|
||||
|
||||
class SharedItemsListAdapter : RecyclerView.Adapter<SharedItemsListAdapter.ViewHolder>() {
|
||||
|
||||
companion object {
|
||||
private val TAG = SharedItemsListAdapter::class.simpleName
|
||||
}
|
||||
|
||||
class ViewHolder(val binding: AttachmentListItemBinding, itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
var authHeader: Map<String, String> = emptyMap()
|
||||
var items: List<SharedItem> = emptyList()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val binding = AttachmentListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding, binding.root)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
||||
val currentItem = items[position]
|
||||
|
||||
holder.binding.fileName.text = currentItem.name
|
||||
|
||||
if (currentItem.previewAvailable) {
|
||||
val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(currentItem.previewLink))
|
||||
.setProgressiveRenderingEnabled(true)
|
||||
.setRotationOptions(RotationOptions.autoRotate())
|
||||
.disableDiskCache()
|
||||
.setHeaders(authHeader)
|
||||
.build()
|
||||
|
||||
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
|
||||
.setOldController(holder.binding.fileImage.controller)
|
||||
.setAutoPlayAnimations(true)
|
||||
.setImageRequest(imageRequest)
|
||||
.build()
|
||||
holder.binding.fileImage.controller = draweeController
|
||||
} else {
|
||||
when (currentItem.mimeType) {
|
||||
"video/mp4",
|
||||
"video/quicktime",
|
||||
"video/ogg"
|
||||
-> holder.binding.fileImage.setImageResource(R.drawable.ic_mimetype_video)
|
||||
"audio/mpeg",
|
||||
"audio/wav",
|
||||
"audio/ogg",
|
||||
-> holder.binding.fileImage.setImageResource(R.drawable.ic_mimetype_audio)
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif"
|
||||
-> holder.binding.fileImage.setImageResource(R.drawable.ic_mimetype_image)
|
||||
"text/markdown",
|
||||
"text/plain"
|
||||
-> holder.binding.fileImage.setImageResource(R.drawable.ic_mimetype_text)
|
||||
else
|
||||
-> holder.binding.fileImage.setImageResource(R.drawable.ic_mimetype_file)
|
||||
}
|
||||
}
|
||||
holder.binding.fileItem.setOnClickListener {
|
||||
val fileViewerUtils = FileViewerUtils(it.context, currentItem.userEntity)
|
||||
|
||||
fileViewerUtils.openFile(
|
||||
currentItem.id,
|
||||
currentItem.name,
|
||||
currentItem.fileSize,
|
||||
currentItem.path,
|
||||
currentItem.link,
|
||||
currentItem.mimeType,
|
||||
holder.binding.progressBar,
|
||||
null,
|
||||
holder.binding.fileImage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
}
|
|
@ -27,13 +27,11 @@
|
|||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
@ -43,44 +41,31 @@ 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.activities.FullScreenImageActivity;
|
||||
import com.nextcloud.talk.activities.FullScreenMediaActivity;
|
||||
import com.nextcloud.talk.activities.FullScreenTextViewerActivity;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
||||
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker;
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet;
|
||||
import com.nextcloud.talk.utils.AccountUtils;
|
||||
import com.nextcloud.talk.utils.DisplayUtils;
|
||||
import com.nextcloud.talk.utils.DrawableUtils;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
import com.nextcloud.talk.utils.FileViewerUtils;
|
||||
import com.stfalcon.chatkit.messages.MessageHolders;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
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.core.content.FileProvider;
|
||||
import androidx.emoji.widget.EmojiTextView;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import autodagger.AutoInjector;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleObserver;
|
||||
|
@ -114,6 +99,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
|
||||
ReactionsInsideMessageBinding reactionsBinding;
|
||||
|
||||
FileViewerUtils fileViewerUtils;
|
||||
|
||||
View clickView;
|
||||
|
||||
ReactionsInterface reactionsInterface;
|
||||
|
@ -138,7 +125,7 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
} else {
|
||||
userAvatar.setVisibility(View.VISIBLE);
|
||||
userAvatar.setOnClickListener(v -> {
|
||||
if (payload instanceof ProfileBottomSheet){
|
||||
if (payload instanceof ProfileBottomSheet) {
|
||||
((ProfileBottomSheet) payload).showFor(message.actorId, v.getContext());
|
||||
}
|
||||
});
|
||||
|
@ -163,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)) {
|
||||
|
@ -179,8 +168,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
if (message.getSelectedIndividualHashMap().containsKey(KEY_CONTACT_PHOTO)) {
|
||||
image = getPreviewContactPhoto();
|
||||
Drawable drawable = getDrawableFromContactDetails(
|
||||
context,
|
||||
message.getSelectedIndividualHashMap().get(KEY_CONTACT_PHOTO));
|
||||
context,
|
||||
message.getSelectedIndividualHashMap().get(KEY_CONTACT_PHOTO));
|
||||
image.getHierarchy().setPlaceholderImage(drawable);
|
||||
} else if (message.getSelectedIndividualHashMap().containsKey(KEY_MIMETYPE)) {
|
||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||
|
@ -191,52 +180,27 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH), message.activeUser);
|
||||
}
|
||||
|
||||
if(message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null){
|
||||
String accountString =
|
||||
message.activeUser.getUsername() + "@" +
|
||||
message.activeUser.getBaseUrl()
|
||||
.replace("https://", "")
|
||||
.replace("http://", "");
|
||||
|
||||
if (message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null) {
|
||||
clickView.setOnClickListener(v -> {
|
||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||
if (isSupportedForInternalViewer(mimetype) || canBeHandledByExternalApp(mimetype, fileName)) {
|
||||
openOrDownloadFile(message);
|
||||
} else {
|
||||
openFileInFilesApp(message, accountString);
|
||||
}
|
||||
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 -> updateViewsByProgress(fileName, mimetype, info));
|
||||
}
|
||||
}
|
||||
} 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());
|
||||
|
@ -273,9 +237,9 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
Drawable drawable = null;
|
||||
if (!base64.equals("")) {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(
|
||||
Base64.decode(base64.getBytes(), Base64.DEFAULT));
|
||||
Base64.decode(base64.getBytes(), Base64.DEFAULT));
|
||||
drawable = Drawable.createFromResourceStream(context.getResources(),
|
||||
null, inputStream, null, null);
|
||||
null, inputStream, null, null);
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
|
@ -287,151 +251,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
return drawable;
|
||||
}
|
||||
|
||||
public abstract EmojiTextView getMessageText();
|
||||
|
||||
public abstract ProgressBar getProgressBar();
|
||||
|
||||
public abstract SimpleDraweeView getImage();
|
||||
|
||||
public abstract View getPreviewContainer();
|
||||
|
||||
public abstract View getPreviewContactContainer();
|
||||
|
||||
public abstract SimpleDraweeView getPreviewContactPhoto();
|
||||
|
||||
public abstract EmojiTextView getPreviewContactName();
|
||||
|
||||
public abstract ProgressBar getPreviewContactProgressBar();
|
||||
|
||||
public abstract ReactionsInsideMessageBinding getReactionsBinding();
|
||||
|
||||
private void openOrDownloadFile(ChatMessage message) {
|
||||
String filename = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||
File file = new File(context.getCacheDir(), filename);
|
||||
if (file.exists()) {
|
||||
openFile(filename, mimetype);
|
||||
} else {
|
||||
downloadFileToCache(message);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSupportedForInternalViewer(String mimetype){
|
||||
switch (mimetype) {
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
case "image/gif":
|
||||
case "audio/mpeg":
|
||||
case "audio/wav":
|
||||
case "audio/ogg":
|
||||
case "video/mp4":
|
||||
case "video/quicktime":
|
||||
case "video/ogg":
|
||||
case "text/markdown":
|
||||
case "text/plain":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void openFile(String filename, String mimetype) {
|
||||
switch (mimetype) {
|
||||
case "audio/mpeg":
|
||||
case "audio/wav":
|
||||
case "audio/ogg":
|
||||
case "video/mp4":
|
||||
case "video/quicktime":
|
||||
case "video/ogg":
|
||||
openMediaView(filename, mimetype);
|
||||
break;
|
||||
case "image/png":
|
||||
case "image/jpeg":
|
||||
case "image/gif":
|
||||
openImageView(filename, mimetype);
|
||||
break;
|
||||
case "text/markdown":
|
||||
case "text/plain":
|
||||
openTextView(filename, mimetype);
|
||||
break;
|
||||
default:
|
||||
openFileByExternalApp(filename, mimetype);
|
||||
}
|
||||
}
|
||||
|
||||
private void openFileByExternalApp(String fileName, String mimetype) {
|
||||
String path = context.getCacheDir().getAbsolutePath() + "/" + fileName;
|
||||
File file = new File(path);
|
||||
Intent intent;
|
||||
if (Build.VERSION.SDK_INT < 24) {
|
||||
intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(file), mimetype);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
} else {
|
||||
intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
Uri pdfURI = FileProvider.getUriForFile(context, context.getPackageName(), file);
|
||||
intent.setDataAndType(pdfURI, mimetype);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
|
||||
try {
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
Log.e(TAG, "No Application found to open the file. This should have been handled beforehand!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error while opening file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canBeHandledByExternalApp(String mimetype, String fileName) {
|
||||
String path = context.getCacheDir().getAbsolutePath() + "/" + fileName;
|
||||
File file = new File(path);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(file), mimetype);
|
||||
return intent.resolveActivity(context.getPackageManager()) != null;
|
||||
}
|
||||
|
||||
private void openImageView(String filename, String mimetype) {
|
||||
Intent fullScreenImageIntent = new Intent(context, FullScreenImageActivity.class);
|
||||
fullScreenImageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
fullScreenImageIntent.putExtra("FILE_NAME", filename);
|
||||
fullScreenImageIntent.putExtra("IS_GIF", isGif(mimetype));
|
||||
context.startActivity(fullScreenImageIntent);
|
||||
}
|
||||
|
||||
private void openFileInFilesApp(ChatMessage message, String accountString) {
|
||||
if (AccountUtils.INSTANCE.canWeOpenFilesApp(context, accountString)) {
|
||||
Intent filesAppIntent = new Intent(Intent.ACTION_VIEW, null);
|
||||
final ComponentName componentName = new ComponentName(
|
||||
context.getString(R.string.nc_import_accounts_from),
|
||||
"com.owncloud.android.ui.activity.FileDisplayActivity"
|
||||
);
|
||||
filesAppIntent.setComponent(componentName);
|
||||
filesAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
filesAppIntent.setPackage(context.getString(R.string.nc_import_accounts_from));
|
||||
filesAppIntent.putExtra(BundleKeys.INSTANCE.getKEY_ACCOUNT(), accountString);
|
||||
filesAppIntent.putExtra(
|
||||
BundleKeys.INSTANCE.getKEY_FILE_ID(),
|
||||
message.getSelectedIndividualHashMap().get(KEY_ID)
|
||||
);
|
||||
context.startActivity(filesAppIntent);
|
||||
} else {
|
||||
Intent browserIntent = new Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(message.getSelectedIndividualHashMap().get("link"))
|
||||
);
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(browserIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void onMessageViewLongClick(ChatMessage message, String accountString) {
|
||||
if (isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
|
||||
private void onMessageViewLongClick(ChatMessage message) {
|
||||
if (fileViewerUtils.isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
|
||||
previewMessageInterface.onPreviewMessageLongClick(message);
|
||||
return;
|
||||
}
|
||||
|
@ -452,132 +273,17 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
popupMenu.inflate(R.menu.chat_preview_message_menu);
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(item -> {
|
||||
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;
|
||||
});
|
||||
|
||||
popupMenu.show();
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private void downloadFileToCache(ChatMessage message) {
|
||||
|
||||
String baseUrl = message.activeUser.getBaseUrl();
|
||||
String userId = message.activeUser.getUserId();
|
||||
String attachmentFolder = CapabilitiesUtil.getAttachmentFolder(message.activeUser);
|
||||
|
||||
String fileName = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
||||
|
||||
String size = message.getSelectedIndividualHashMap().get("size");
|
||||
|
||||
if (size == null) {
|
||||
size = "-1";
|
||||
}
|
||||
Integer fileSize = Integer.valueOf(size);
|
||||
|
||||
String fileId = message.getSelectedIndividualHashMap().get(KEY_ID);
|
||||
String path = message.getSelectedIndividualHashMap().get(KEY_PATH);
|
||||
|
||||
// check if download worker is already running
|
||||
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) {
|
||||
Log.d(TAG, "Download worker for " + fileId + " is already running or " +
|
||||
"scheduled");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Log.e(TAG, "Error when checking if worker already exsists", e);
|
||||
}
|
||||
|
||||
Data data;
|
||||
OneTimeWorkRequest downloadWorker;
|
||||
|
||||
data = new Data.Builder()
|
||||
.putString(DownloadFileToCacheWorker.KEY_BASE_URL, baseUrl)
|
||||
.putString(DownloadFileToCacheWorker.KEY_USER_ID, userId)
|
||||
.putString(DownloadFileToCacheWorker.KEY_ATTACHMENT_FOLDER, attachmentFolder)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(DownloadFileToCacheWorker.KEY_FILE_PATH, path)
|
||||
.putInt(DownloadFileToCacheWorker.KEY_FILE_SIZE, fileSize)
|
||||
.build();
|
||||
|
||||
downloadWorker = new OneTimeWorkRequest.Builder(DownloadFileToCacheWorker.class)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build();
|
||||
|
||||
WorkManager.getInstance().enqueue(downloadWorker);
|
||||
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(downloadWorker.getId()).observeForever(workInfo -> {
|
||||
updateViewsByProgress(fileName, mimetype, workInfo);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateViewsByProgress(String fileName, String mimetype, WorkInfo workInfo) {
|
||||
switch (workInfo.getState()) {
|
||||
case RUNNING:
|
||||
int progress = workInfo.getProgress().getInt(DownloadFileToCacheWorker.PROGRESS, -1);
|
||||
if (progress > -1) {
|
||||
getMessageText().setText(String.format(context.getResources().getString(R.string.filename_progress), fileName, progress));
|
||||
}
|
||||
break;
|
||||
|
||||
case SUCCEEDED:
|
||||
if (image.isShown()) {
|
||||
openFile(fileName, mimetype);
|
||||
} else {
|
||||
Log.d(TAG, "file " + fileName +
|
||||
" was downloaded but it's not opened because view is not shown on screen");
|
||||
}
|
||||
getMessageText().setText(fileName);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case FAILED:
|
||||
getMessageText().setText(fileName);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void openMediaView(String filename, String mimetype) {
|
||||
Intent fullScreenMediaIntent = new Intent(context, FullScreenMediaActivity.class);
|
||||
fullScreenMediaIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
fullScreenMediaIntent.putExtra("FILE_NAME", filename);
|
||||
fullScreenMediaIntent.putExtra("AUDIO_ONLY", isAudioOnly(mimetype));
|
||||
context.startActivity(fullScreenMediaIntent);
|
||||
}
|
||||
|
||||
private void openTextView(String filename, String mimetype) {
|
||||
Intent fullScreenTextViewerIntent = new Intent(context, FullScreenTextViewerActivity.class);
|
||||
fullScreenTextViewerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
fullScreenTextViewerIntent.putExtra("FILE_NAME", filename);
|
||||
fullScreenTextViewerIntent.putExtra("IS_MARKDOWN", isMarkdown(mimetype));
|
||||
context.startActivity(fullScreenTextViewerIntent);
|
||||
}
|
||||
|
||||
private boolean isGif(String mimetype) {
|
||||
return ("image/gif").equals(mimetype);
|
||||
}
|
||||
|
||||
private boolean isMarkdown(String mimetype) {
|
||||
return ("text/markdown").equals(mimetype);
|
||||
}
|
||||
|
||||
private boolean isAudioOnly(String mimetype) {
|
||||
return mimetype.startsWith("audio");
|
||||
}
|
||||
|
||||
private void fetchFileInformation(String url, UserEntity activeUser) {
|
||||
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
||||
@Override
|
||||
|
@ -585,34 +291,34 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
return new ReadFilesystemOperation(okHttpClient, activeUser, url, 0);
|
||||
}
|
||||
}).observeOn(Schedulers.io())
|
||||
.subscribe(new SingleObserver<ReadFilesystemOperation>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
// unused atm
|
||||
}
|
||||
.subscribe(new SingleObserver<ReadFilesystemOperation>() {
|
||||
@Override
|
||||
public void onSubscribe(@NonNull Disposable d) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
|
||||
DavResponse davResponse = readFilesystemOperation.readRemotePath();
|
||||
if (davResponse.data != null) {
|
||||
List<BrowserFile> browserFileList = (List<BrowserFile>) davResponse.data;
|
||||
if (!browserFileList.isEmpty()) {
|
||||
new Handler(context.getMainLooper()).post(() -> {
|
||||
int resourceId = DrawableUtils
|
||||
.INSTANCE
|
||||
.getDrawableResourceIdForMimeType(browserFileList.get(0).mimeType);
|
||||
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
|
||||
image.getHierarchy().setPlaceholderImage(drawable);
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
|
||||
DavResponse davResponse = readFilesystemOperation.readRemotePath();
|
||||
if (davResponse.data != null) {
|
||||
List<BrowserFile> browserFileList = (List<BrowserFile>) davResponse.data;
|
||||
if (!browserFileList.isEmpty()) {
|
||||
new Handler(context.getMainLooper()).post(() -> {
|
||||
int resourceId = DrawableUtils
|
||||
.INSTANCE
|
||||
.getDrawableResourceIdForMimeType(browserFileList.get(0).mimeType);
|
||||
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
|
||||
image.getHierarchy().setPlaceholderImage(drawable);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(TAG, "Error reading file information", e);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e(TAG, "Error reading file information", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void assignReactionInterface(ReactionsInterface reactionsInterface) {
|
||||
|
@ -622,4 +328,22 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
|||
public void assignPreviewMessageInterface(PreviewMessageInterface previewMessageInterface) {
|
||||
this.previewMessageInterface = previewMessageInterface;
|
||||
}
|
||||
|
||||
public abstract EmojiTextView getMessageText();
|
||||
|
||||
public abstract ProgressBar getProgressBar();
|
||||
|
||||
public abstract SimpleDraweeView getImage();
|
||||
|
||||
public abstract View getPreviewContainer();
|
||||
|
||||
public abstract View getPreviewContactContainer();
|
||||
|
||||
public abstract SimpleDraweeView getPreviewContactPhoto();
|
||||
|
||||
public abstract EmojiTextView getPreviewContactName();
|
||||
|
||||
public abstract ProgressBar getPreviewContactProgressBar();
|
||||
|
||||
public abstract ReactionsInsideMessageBinding getReactionsBinding();
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ package com.nextcloud.talk.api;
|
|||
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
|
||||
import com.nextcloud.talk.models.json.chat.ChatOverall;
|
||||
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
|
||||
import com.nextcloud.talk.models.json.chat.ChatShareOverall;
|
||||
import com.nextcloud.talk.models.json.conversations.RoomOverall;
|
||||
import com.nextcloud.talk.models.json.conversations.RoomsOverall;
|
||||
import com.nextcloud.talk.models.json.generic.GenericOverall;
|
||||
|
@ -338,6 +339,12 @@ public interface NcApi {
|
|||
@Field("actorDisplayName") String actorDisplayName,
|
||||
@Field("replyTo") Integer replyTo);
|
||||
|
||||
@GET
|
||||
Observable<Response<ChatShareOverall>> getSharedItems(@Header("Authorization") String authorization, @Url String url,
|
||||
@Query("objectType") String objectType,
|
||||
@Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId,
|
||||
@Nullable @Query("limit") Integer limit);
|
||||
|
||||
@GET
|
||||
Observable<MentionOverall> getMentionAutocompleteSuggestions(@Header("Authorization") String authorization,
|
||||
@Url String url, @Query("search") String query,
|
||||
|
|
|
@ -46,6 +46,7 @@ import android.os.Build
|
|||
import android.os.Build.VERSION_CODES.O
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Parcelable
|
||||
import android.os.SystemClock
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
|
@ -99,6 +100,7 @@ import com.nextcloud.talk.BuildConfig
|
|||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.CallActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.activities.SharedItemsActivity
|
||||
import com.nextcloud.talk.activities.TakePhotoActivity
|
||||
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
|
||||
import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
|
||||
|
@ -156,6 +158,7 @@ import com.nextcloud.talk.utils.NotificationUtils
|
|||
import com.nextcloud.talk.utils.UriUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACTIVE_CONVERSATION
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
|
||||
|
@ -188,9 +191,7 @@ import java.io.File
|
|||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.ArrayList
|
||||
import java.util.Date
|
||||
import java.util.HashMap
|
||||
import java.util.Objects
|
||||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
|
@ -253,6 +254,7 @@ class ChatController(args: Bundle) :
|
|||
var conversationInfoMenuItem: MenuItem? = null
|
||||
var conversationVoiceCallMenuItem: MenuItem? = null
|
||||
var conversationVideoMenuItem: MenuItem? = null
|
||||
var conversationSharedItemsItem: MenuItem? = null
|
||||
|
||||
var magicWebSocketInstance: MagicWebSocketInstance? = null
|
||||
|
||||
|
@ -1464,7 +1466,7 @@ class ChatController(args: Bundle) :
|
|||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
router.pushController(
|
||||
RouterTransaction.with(BrowserForSharingController(bundle))
|
||||
.pushChangeHandler(VerticalChangeHandler())
|
||||
|
@ -1476,7 +1478,7 @@ class ChatController(args: Bundle) :
|
|||
Log.d(TAG, "showShareLocationScreen")
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
router.pushController(
|
||||
RouterTransaction.with(LocationPickerController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
|
@ -1487,7 +1489,7 @@ class ChatController(args: Bundle) :
|
|||
private fun showConversationInfoScreen() {
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, inOneToOneCall())
|
||||
router.pushController(
|
||||
RouterTransaction.with(ConversationInfoController(bundle))
|
||||
|
@ -2299,6 +2301,7 @@ class ChatController(args: Bundle) :
|
|||
conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
|
||||
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
|
||||
conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
|
||||
conversationSharedItemsItem = menu.findItem(R.id.shared_items)
|
||||
|
||||
loadAvatarForStatusBar()
|
||||
}
|
||||
|
@ -2337,10 +2340,22 @@ class ChatController(args: Bundle) :
|
|||
showConversationInfoScreen()
|
||||
return true
|
||||
}
|
||||
R.id.shared_items -> {
|
||||
showSharedItems()
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSharedItems() {
|
||||
val intent = Intent(activity, SharedItemsActivity::class.java)
|
||||
intent.putExtra(KEY_CONVERSATION_NAME, currentConversation?.displayName)
|
||||
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
|
||||
intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
|
||||
activity!!.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
||||
val chatMessageIterator = chatMessageMap.iterator()
|
||||
|
@ -2402,7 +2417,7 @@ class ChatController(args: Bundle) :
|
|||
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
|
||||
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
|
||||
bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl)
|
||||
bundle.putString(BundleKeys.KEY_CONVERSATION_NAME, it.displayName)
|
||||
bundle.putString(KEY_CONVERSATION_NAME, it.displayName)
|
||||
|
||||
if (isVoiceOnlyCall) {
|
||||
bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true)
|
||||
|
|
|
@ -27,9 +27,11 @@
|
|||
package com.nextcloud.talk.controllers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
|
@ -49,6 +51,7 @@ import com.bluelinelabs.conductor.RouterTransaction
|
|||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.facebook.drawee.backends.pipeline.Fresco
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.SharedItemsActivity
|
||||
import com.nextcloud.talk.adapters.items.ParticipantItem
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
|
@ -88,11 +91,8 @@ import io.reactivex.schedulers.Schedulers
|
|||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.ArrayList
|
||||
import java.util.Calendar
|
||||
import java.util.Collections
|
||||
import java.util.Comparator
|
||||
import java.util.HashMap
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -175,10 +175,19 @@ class ConversationInfoController(args: Bundle) :
|
|||
binding.leaveConversationAction.setOnClickListener { leaveConversation() }
|
||||
binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog(null) }
|
||||
binding.addParticipantsAction.setOnClickListener { addParticipants() }
|
||||
binding.showSharedItemsAction.setOnClickListener { showSharedItems() }
|
||||
|
||||
fetchRoomInfo()
|
||||
}
|
||||
|
||||
private fun showSharedItems() {
|
||||
val intent = Intent(activity, SharedItemsActivity::class.java)
|
||||
intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
|
||||
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
|
||||
intent.putExtra(BundleKeys.KEY_USER_ENTITY, conversationUser as Parcelable)
|
||||
activity!!.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onViewBound(view: View) {
|
||||
super.onViewBound(view)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.chat;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
import com.nextcloud.talk.models.json.generic.GenericOCS;
|
||||
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
@Parcel
|
||||
@JsonObject
|
||||
public class ChatShareOCS {
|
||||
@JsonField(name = "data")
|
||||
public HashMap<String, ChatMessage> data;
|
||||
|
||||
public HashMap<String, ChatMessage> getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void setData(HashMap<String, ChatMessage> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ChatShareOCS)) {
|
||||
return false;
|
||||
}
|
||||
final ChatShareOCS other = (ChatShareOCS) o;
|
||||
if (!other.canEqual(this)) {
|
||||
return false;
|
||||
}
|
||||
final Object this$data = this.getData();
|
||||
final Object other$data = other.getData();
|
||||
|
||||
return Objects.equals(this$data, other$data);
|
||||
}
|
||||
|
||||
protected boolean canEqual(final Object other) {
|
||||
return other instanceof ChatShareOCS;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
final int PRIME = 59;
|
||||
int result = 1;
|
||||
final Object $data = this.getData();
|
||||
return result * PRIME + ($data == null ? 43 : $data.hashCode());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ChatShareOCS(data=" + this.getData() + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.models.json.chat;
|
||||
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField;
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject;
|
||||
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Parcel
|
||||
@JsonObject
|
||||
public class ChatShareOverall {
|
||||
@JsonField(name = "ocs")
|
||||
public ChatShareOCS ocs;
|
||||
|
||||
public ChatShareOCS getOcs() {
|
||||
return this.ocs;
|
||||
}
|
||||
|
||||
public void setOcs(ChatShareOCS ocs) {
|
||||
this.ocs = ocs;
|
||||
}
|
||||
|
||||
public boolean equals(final Object o) {
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ChatShareOverall)) {
|
||||
return false;
|
||||
}
|
||||
final ChatShareOverall other = (ChatShareOverall) o;
|
||||
if (!other.canEqual(this)) {
|
||||
return false;
|
||||
}
|
||||
final Object this$ocs = this.getOcs();
|
||||
final Object other$ocs = other.getOcs();
|
||||
|
||||
return Objects.equals(this$ocs, other$ocs);
|
||||
}
|
||||
|
||||
protected boolean canEqual(final Object other) {
|
||||
return other instanceof ChatShareOverall;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
final int PRIME = 59;
|
||||
int result = 1;
|
||||
final Object $ocs = this.getOcs();
|
||||
return result * PRIME + ($ocs == null ? 43 : $ocs.hashCode());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ChatShareOverall(ocs=" + this.getOcs() + ")";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.nextcloud.talk.repositories
|
||||
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
|
||||
data class SharedItem(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val fileSize: Int,
|
||||
val path: String,
|
||||
val link: String,
|
||||
val mimeType: String,
|
||||
val previewAvailable: Boolean,
|
||||
val previewLink: String,
|
||||
val userEntity: UserEntity,
|
||||
)
|
|
@ -0,0 +1,64 @@
|
|||
package com.nextcloud.talk.repositories
|
||||
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.R
|
||||
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
|
||||
import retrofit2.Response
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class SharedItemsRepository {
|
||||
|
||||
companion object {
|
||||
private val TAG = SharedItemsRepository::class.simpleName
|
||||
}
|
||||
|
||||
var parameters: Parameters? = null
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
init {
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
}
|
||||
|
||||
fun media(type: String): Observable<Response<ChatShareOverall>>? {
|
||||
return media(type, null)
|
||||
}
|
||||
|
||||
fun media(type: String, lastKnownMessageId: Int?): Observable<Response<ChatShareOverall>>? {
|
||||
val credentials = ApiUtils.getCredentials(parameters!!.userName, parameters!!.userToken)
|
||||
|
||||
return ncApi.getSharedItems(
|
||||
credentials,
|
||||
ApiUtils.getUrlForChatSharedItems(1, parameters!!.baseUrl, parameters!!.roomToken),
|
||||
type, lastKnownMessageId, 28
|
||||
)
|
||||
}
|
||||
|
||||
fun authHeader(): Map<String, String> {
|
||||
return mapOf(Pair("Authorization", ApiUtils.getCredentials(parameters!!.userName, parameters!!.userToken)))
|
||||
}
|
||||
|
||||
fun previewLink(fileId: String?): String {
|
||||
return ApiUtils.getUrlForFilePreviewWithFileId(
|
||||
parameters!!.baseUrl,
|
||||
fileId,
|
||||
sharedApplication!!.resources.getDimensionPixelSize(R.dimen.maximum_file_preview_size)
|
||||
)
|
||||
}
|
||||
|
||||
data class Parameters(
|
||||
val userName: String,
|
||||
val userToken: String,
|
||||
val baseUrl: String,
|
||||
val userEntity: UserEntity,
|
||||
val roomToken: String
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.nextcloud.talk.repositories
|
||||
|
||||
class SharedMediaItems(
|
||||
val type: String,
|
||||
val items: MutableList<SharedItem>,
|
||||
var lastSeenId: Int?,
|
||||
var moreItemsExisting: Boolean,
|
||||
val authHeader: Map<String, String>
|
||||
)
|
|
@ -260,6 +260,10 @@ public class ApiUtils {
|
|||
public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
|
||||
return getUrlForChat(version, baseUrl, token) + "/" + messageId;
|
||||
}
|
||||
|
||||
public static String getUrlForChatSharedItems(int version, String baseUrl, String token) {
|
||||
return getUrlForChat(version, baseUrl, token) + "/share";
|
||||
}
|
||||
|
||||
public static String getUrlForSignaling(int version, String baseUrl) {
|
||||
return getUrlForApi(version, baseUrl) + "/signaling";
|
||||
|
|
397
app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt
Normal file
397
app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt
Normal file
|
@ -0,0 +1,397 @@
|
|||
package com.nextcloud.talk.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.emoji.widget.EmojiTextView
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.FullScreenImageActivity
|
||||
import com.nextcloud.talk.activities.FullScreenMediaActivity
|
||||
import com.nextcloud.talk.activities.FullScreenTextViewerActivity
|
||||
import com.nextcloud.talk.adapters.messages.MagicPreviewMessageViewHolder
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
|
||||
import com.nextcloud.talk.models.database.CapabilitiesUtil
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ACCOUNT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FILE_ID
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
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(
|
||||
fileName,
|
||||
fileId,
|
||||
path,
|
||||
fileSize,
|
||||
mimetype,
|
||||
progressBar,
|
||||
messageText,
|
||||
previewImage
|
||||
)
|
||||
} else {
|
||||
openFileInFilesApp(link, fileId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun canBeHandledByExternalApp(mimetype: String, fileName: String): Boolean {
|
||||
val path: String = context.cacheDir.absolutePath + "/" + fileName
|
||||
val file = File(path)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(Uri.fromFile(file), mimetype)
|
||||
return intent.resolveActivity(context.packageManager) != null
|
||||
}
|
||||
|
||||
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()) {
|
||||
openFileByMimetype(fileName!!, mimetype!!)
|
||||
} else {
|
||||
downloadFileToCache(
|
||||
fileName,
|
||||
fileId,
|
||||
path,
|
||||
fileSize,
|
||||
mimetype,
|
||||
progressBar,
|
||||
messageText,
|
||||
previewImage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openFileByExternalApp(fileName: String, mimetype: String) {
|
||||
val path = context.cacheDir.absolutePath + "/" + fileName
|
||||
val file = File(path)
|
||||
val intent: Intent
|
||||
if (Build.VERSION.SDK_INT < 24) {
|
||||
intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(Uri.fromFile(file), mimetype)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
|
||||
} else {
|
||||
intent = Intent()
|
||||
intent.action = Intent.ACTION_VIEW
|
||||
val pdfURI = FileProvider.getUriForFile(context, context.packageName, file)
|
||||
intent.setDataAndType(pdfURI, mimetype)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
try {
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
Log.e(TAG, "No Application found to open the file. This should have been handled beforehand!")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error while opening file", e)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
context.getString(R.string.nc_import_accounts_from),
|
||||
"com.owncloud.android.ui.activity.FileDisplayActivity"
|
||||
)
|
||||
filesAppIntent.component = componentName
|
||||
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, keyID)
|
||||
context.startActivity(filesAppIntent)
|
||||
} else {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(link)
|
||||
)
|
||||
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openImageView(filename: String, mimetype: String) {
|
||||
val fullScreenImageIntent = Intent(context, FullScreenImageActivity::class.java)
|
||||
fullScreenImageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
fullScreenImageIntent.putExtra("FILE_NAME", filename)
|
||||
fullScreenImageIntent.putExtra("IS_GIF", isGif(mimetype))
|
||||
context.startActivity(fullScreenImageIntent)
|
||||
}
|
||||
|
||||
private fun openMediaView(filename: String, mimetype: String) {
|
||||
val fullScreenMediaIntent = Intent(context, FullScreenMediaActivity::class.java)
|
||||
fullScreenMediaIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
fullScreenMediaIntent.putExtra("FILE_NAME", filename)
|
||||
fullScreenMediaIntent.putExtra("AUDIO_ONLY", isAudioOnly(mimetype))
|
||||
context.startActivity(fullScreenMediaIntent)
|
||||
}
|
||||
|
||||
private fun openTextView(filename: String, mimetype: String) {
|
||||
val fullScreenTextViewerIntent = Intent(context, FullScreenTextViewerActivity::class.java)
|
||||
fullScreenTextViewerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
fullScreenTextViewerIntent.putExtra("FILE_NAME", filename)
|
||||
fullScreenTextViewerIntent.putExtra("IS_MARKDOWN", isMarkdown(mimetype))
|
||||
context.startActivity(fullScreenTextViewerIntent)
|
||||
}
|
||||
|
||||
fun isSupportedForInternalViewer(mimetype: String?): Boolean {
|
||||
return when (mimetype) {
|
||||
"image/png", "image/jpeg",
|
||||
"image/gif", "audio/mpeg",
|
||||
"audio/wav", "audio/ogg",
|
||||
"video/mp4", "video/quicktime",
|
||||
"video/ogg", "text/markdown",
|
||||
"text/plain" -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isGif(mimetype: String): Boolean {
|
||||
return "image/gif" == mimetype
|
||||
}
|
||||
|
||||
private fun isMarkdown(mimetype: String): Boolean {
|
||||
return "text/markdown" == mimetype
|
||||
}
|
||||
|
||||
private fun isAudioOnly(mimetype: String): Boolean {
|
||||
return mimetype.startsWith("audio")
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
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!!
|
||||
)
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
Log.d(TAG, "Download worker for $fileId is already running or scheduled")
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exsists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exsists", e)
|
||||
}
|
||||
val downloadWorker: OneTimeWorkRequest
|
||||
val data: Data = Data.Builder()
|
||||
.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)
|
||||
.build()
|
||||
downloadWorker = OneTimeWorkRequest.Builder(DownloadFileToCacheWorker::class.java)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueue(downloadWorker)
|
||||
progressBar?.visibility = View.VISIBLE
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(downloadWorker.id)
|
||||
.observeForever { workInfo: WorkInfo? ->
|
||||
updateViewsByProgress(
|
||||
fileName,
|
||||
mimetype,
|
||||
workInfo!!,
|
||||
progressBar,
|
||||
messageText,
|
||||
previewImage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateViewsByProgress(
|
||||
fileName: String,
|
||||
mimetype: String,
|
||||
workInfo: WorkInfo,
|
||||
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(
|
||||
context.resources.getString(R.string.filename_progress),
|
||||
fileName,
|
||||
progress
|
||||
)
|
||||
}
|
||||
}
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
if (previewImage.isShown) {
|
||||
openFileByMimetype(fileName, mimetype)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
WorkInfo.State.FAILED -> {
|
||||
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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package com.nextcloud.talk.viewmodels
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.json.chat.ChatShareOverall
|
||||
import com.nextcloud.talk.repositories.SharedItem
|
||||
import com.nextcloud.talk.repositories.SharedItemsRepository
|
||||
import com.nextcloud.talk.repositories.SharedMediaItems
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import retrofit2.Response
|
||||
|
||||
class SharedItemsViewModel(private val repository: SharedItemsRepository, private val initialType: String) :
|
||||
ViewModel() {
|
||||
|
||||
private val _sharedItems: MutableLiveData<SharedMediaItems> by lazy {
|
||||
MutableLiveData<SharedMediaItems>().also {
|
||||
loadItems(initialType)
|
||||
}
|
||||
}
|
||||
|
||||
val sharedItems: LiveData<SharedMediaItems>
|
||||
get() = _sharedItems
|
||||
|
||||
fun loadNextItems() {
|
||||
val currentSharedItems = sharedItems.value!!
|
||||
|
||||
if (currentSharedItems.moreItemsExisting) {
|
||||
repository.media(currentSharedItems.type, currentSharedItems.lastSeenId)?.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(observer(currentSharedItems.type, false))
|
||||
}
|
||||
}
|
||||
|
||||
fun loadItems(type: String) {
|
||||
repository.media(type)?.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(observer(type, true))
|
||||
}
|
||||
|
||||
private fun observer(type: String, initModel: Boolean): Observer<Response<ChatShareOverall>> {
|
||||
return object : Observer<Response<ChatShareOverall>> {
|
||||
|
||||
var chatLastGiven: Int? = null
|
||||
val items = mutableMapOf<String, SharedItem>()
|
||||
|
||||
override fun onSubscribe(d: Disposable) = Unit
|
||||
|
||||
override fun onNext(response: Response<ChatShareOverall>) {
|
||||
|
||||
if (response.headers()["x-chat-last-given"] != null) {
|
||||
chatLastGiven = response.headers()["x-chat-last-given"]!!.toInt()
|
||||
}
|
||||
|
||||
val mediaItems = response.body()!!.ocs!!.data
|
||||
if (mediaItems != null) {
|
||||
for (it in mediaItems) {
|
||||
if (it.value.messageParameters.containsKey("file")) {
|
||||
val fileParameters = it.value.messageParameters["file"]!!
|
||||
|
||||
val previewAvailable =
|
||||
"yes".equals(fileParameters["preview-available"]!!, ignoreCase = true)
|
||||
|
||||
items[it.value.id] = SharedItem(
|
||||
fileParameters["id"]!!,
|
||||
fileParameters["name"]!!,
|
||||
fileParameters["size"]!!.toInt(),
|
||||
fileParameters["path"]!!,
|
||||
fileParameters["link"]!!,
|
||||
fileParameters["mimetype"]!!,
|
||||
previewAvailable,
|
||||
repository.previewLink(fileParameters["id"]),
|
||||
repository.parameters!!.userEntity
|
||||
)
|
||||
} else {
|
||||
Log.w(TAG, "location and deckcard are not yet supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.d(TAG, "An error occurred: $e")
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
|
||||
val sortedMutableItems = items.toSortedMap().values.toList().reversed().toMutableList()
|
||||
val moreItemsExisting = items.count() == 28
|
||||
|
||||
if (initModel) {
|
||||
this@SharedItemsViewModel._sharedItems.value =
|
||||
SharedMediaItems(
|
||||
type,
|
||||
sortedMutableItems,
|
||||
chatLastGiven,
|
||||
moreItemsExisting,
|
||||
repository.authHeader()
|
||||
)
|
||||
} else {
|
||||
val oldItems = this@SharedItemsViewModel._sharedItems.value!!.items
|
||||
this@SharedItemsViewModel._sharedItems.value =
|
||||
SharedMediaItems(
|
||||
type,
|
||||
(oldItems.toMutableList() + sortedMutableItems) as MutableList<SharedItem>,
|
||||
chatLastGiven,
|
||||
moreItemsExisting,
|
||||
repository.authHeader()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(val userEntity: UserEntity, val roomToken: String, private val initialType: String) :
|
||||
ViewModelProvider
|
||||
.Factory {
|
||||
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(SharedItemsViewModel::class.java)) {
|
||||
|
||||
val repository = SharedItemsRepository()
|
||||
repository.parameters = SharedItemsRepository.Parameters(
|
||||
userEntity.userId,
|
||||
userEntity.token,
|
||||
userEntity.baseUrl,
|
||||
userEntity,
|
||||
roomToken
|
||||
)
|
||||
|
||||
return SharedItemsViewModel(repository, initialType) as T
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = SharedItemsViewModel::class.simpleName
|
||||
}
|
||||
}
|
25
app/src/main/res/drawable/ic_folder_multiple_image.xml
Normal file
25
app/src/main/res/drawable/ic_folder_multiple_image.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
@author Google LLC
|
||||
Copyright (C) 2022 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M7,15L11.5,9L15,13.5L17.5,10.5L21,15M22,4H14L12,2H6A2,2 0 0,0 4,4V16A2,2 0 0,0 6,18H22A2,2 0 0,0 24,16V6A2,2 0 0,0 22,4M2,6H0V11H0V20A2,2 0 0,0 2,22H20V20H2V6Z" />
|
||||
</vector>
|
67
app/src/main/res/layout/activity_shared_items.xml
Normal file
67
app/src/main/res/layout/activity_shared_items.xml
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tim Krüger
|
||||
~ @author Andy Scherzinger
|
||||
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||
~ Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/bg_default"
|
||||
tools:context=".activities.SharedItemsActivity">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/shared_items_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/appbar"
|
||||
android:theme="?attr/actionBarPopupTheme"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_scrollFlags="enterAlwaysCollapsed|noScroll"
|
||||
app:navigationIconTint="@color/fontAppbar"
|
||||
app:popupTheme="@style/appActionBarPopupMenu"
|
||||
app:titleTextColor="@color/fontAppbar"
|
||||
tools:title="@string/nc_app_product_name" />
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/shared_items_tabs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/min_size_clickable_area"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@color/appbar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/shared_items_toolbar"
|
||||
app:tabGravity="fill"
|
||||
app:tabMode="scrollable"
|
||||
app:tabTextAppearance="@style/TextAppearanceTab" />
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/image_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/shared_items_tabs"
|
||||
tools:listitem="@layout/attachment_item" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
64
app/src/main/res/layout/attachment_item.xml
Normal file
64
app/src/main/res/layout/attachment_item.xml
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tim Krüger
|
||||
~ @author Marcel Hibbe
|
||||
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||
~ Copyright (C) 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:fresco="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/preview_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true">
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_mimetype_file"
|
||||
app:placeholderImageScaleType="fitCenter"
|
||||
fresco:actualImageScaleType="centerCrop"
|
||||
fresco:failureImage="@drawable/ic_mimetype_file"
|
||||
fresco:placeholderImage="@drawable/ic_mimetype_file"
|
||||
fresco:roundedCornerRadius="4dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
90
app/src/main/res/layout/attachment_list_item.xml
Normal file
90
app/src/main/res/layout/attachment_list_item.xml
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Nextcloud Talk application
|
||||
~
|
||||
~ @author Tim Krüger
|
||||
~ Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/file_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/standard_margin"
|
||||
android:layout_marginTop="@dimen/standard_half_margin"
|
||||
android:layout_marginEnd="@dimen/standard_margin"
|
||||
android:layout_marginBottom="@dimen/standard_half_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/preview_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:adjustViewBounds="true"
|
||||
app:layout_alignSelf="flex_start"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="true">
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
xmlns:fresco="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/file_image"
|
||||
android:layout_width="@dimen/file_icon_size"
|
||||
android:layout_height="@dimen/file_icon_size"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/ic_mimetype_file"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:placeholderImageScaleType="fitCenter"
|
||||
fresco:actualImageScaleType="centerCrop"
|
||||
fresco:failureImage="@drawable/ic_mimetype_file"
|
||||
fresco:placeholderImage="@drawable/ic_mimetype_file"
|
||||
fresco:roundedCornerRadius="4dp"
|
||||
tools:src="@drawable/ic_call_black_24dp"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.emoji.widget.EmojiTextView
|
||||
android:id="@+id/file_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@id/preview_container"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/ListItem"
|
||||
tools:text="Filename" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
android:id="@+id/participants_list_category"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/settings"
|
||||
android:layout_below="@+id/category_shared_items"
|
||||
android:visibility="gone"
|
||||
apc:cardBackgroundColor="@color/bg_default"
|
||||
apc:cardElevation="0dp"
|
||||
|
@ -213,6 +213,29 @@
|
|||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.yarolegovich.mp.MaterialPreferenceCategory
|
||||
android:id="@+id/category_shared_items"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/settings"
|
||||
android:animateLayoutChanges="true"
|
||||
apc:cardBackgroundColor="@color/bg_default"
|
||||
apc:cardElevation="0dp"
|
||||
apc:mpc_title="Shared Items">
|
||||
|
||||
<com.yarolegovich.mp.MaterialStandardPreference
|
||||
android:id="@+id/show_shared_items_action"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
apc:mp_icon="@drawable/ic_folder_multiple_image"
|
||||
apc:mp_icon_tint="@color/grey_600"
|
||||
apc:mp_summary="See all shared photos, voice messages, files, etc."
|
||||
apc:mp_title="Shared Items" />
|
||||
|
||||
</com.yarolegovich.mp.MaterialPreferenceCategory>
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -37,8 +37,13 @@
|
|||
|
||||
<item
|
||||
android:id="@+id/conversation_info"
|
||||
android:icon="@drawable/ic_info_white_24dp"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/nc_conversation_menu_conversation_info"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/shared_items"
|
||||
android:orderInCategory="1"
|
||||
android:title="Shared Items"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<dimen name="avatar_size">40dp</dimen>
|
||||
<dimen name="avatar_size_app_bar">30dp</dimen>
|
||||
<dimen name="avatar_size_big">96dp</dimen>
|
||||
<dimen name="file_icon_size">40dp</dimen>
|
||||
|
||||
<dimen name="chat_text_size">14sp</dimen>
|
||||
<dimen name="message_bubble_corners_radius">6dp</dimen>
|
||||
|
|
|
@ -89,7 +89,6 @@
|
|||
<string name="nc_settings_server_eol">The server version is too old and not supported by this version of the Android app</string>
|
||||
<string name="nc_settings_server_almost_eol">The server version is very old and will not be supported in the next release!</string>
|
||||
<string name="nc_settings_warning">Warning</string>
|
||||
<string name="nc_add">Add</string>
|
||||
<string name="nc_settings_wrong_account">Only current account can be reauthorized</string>
|
||||
<string name="nc_settings_no_talk_installed">Talk app is not installed on the server you tried to authenticate against</string>
|
||||
<string name="nc_settings_account_updated">Your already existing account was updated, instead of adding a new one</string>
|
||||
|
@ -118,7 +117,6 @@
|
|||
<string name="nc_settings_screen_lock_desc">Lock %1$s with Android screen lock or supported biometric method</string>
|
||||
<string name="nc_settings_screen_lock_key" translatable="false">screen_lock</string>
|
||||
<string name="nc_settings_screen_lock_timeout_title">Screen lock inactivity timeout</string>
|
||||
<string name="nc_none">None</string>
|
||||
<string name="nc_settings_screen_lock_timeout_key" translatable="false">screen_lock_timeout</string>
|
||||
<string name="nc_settings_screen_security_title">Screen security</string>
|
||||
<string name="nc_settings_screen_security_desc">Prevents screenshots in the recent list and inside the app</string>
|
||||
|
@ -205,8 +203,6 @@
|
|||
<string name="nc_call_incoming">INCOMING</string>
|
||||
<string name="nc_call_ringing">RINGING</string>
|
||||
<string name="nc_connecting_call">Connecting…</string>
|
||||
<string name="nc_calling">Calling…</string>
|
||||
<string name="nc_incoming_call">Incoming call from</string>
|
||||
<string name="nc_nick_guest">Guest</string>
|
||||
<string name="nc_public_call">New public conversation</string>
|
||||
<string name="nc_public_call_explanation">Public conversations let you invite people from outside through a specially crafted link.</string>
|
||||
|
@ -338,7 +334,6 @@
|
|||
<!-- Empty states -->
|
||||
<string name="nc_conversations_empty">Join a conversation or start a new one</string>
|
||||
<string name="nc_conversations_empty_details">Say hi to your friends and colleagues!</string>
|
||||
<string name="nc_hello">Hello</string>
|
||||
|
||||
<!-- Other -->
|
||||
<string name="nc_limit_hit">%s characters limit has been hit</string>
|
||||
|
@ -379,14 +374,6 @@
|
|||
<string name="nc_lobby_start_soon">The meeting will start soon</string>
|
||||
<string name="nc_manual">Not set</string>
|
||||
|
||||
<!-- Errors -->
|
||||
<string name="nc_no_connection_error">No connection</string>
|
||||
<string name="nc_bad_response_error">Bad response</string>
|
||||
<string name="nc_timeout_error">Timeout</string>
|
||||
<string name="nc_empty_response_error">Empty response</string>
|
||||
<string name="nc_not_defined_error">Unknown error</string>
|
||||
<string name="nc_unauthorized_error">Unauthorized</string>
|
||||
|
||||
<string name="nc_allow_guests">Allow guests</string>
|
||||
<string name="nc_last_moderator_title">Could not leave conversation</string>
|
||||
<string name="nc_last_moderator">You need to promote a new moderator before you can leave %1$s.</string>
|
||||
|
@ -430,6 +417,9 @@
|
|||
<string name="nc_share_contact">Share contact</string>
|
||||
<string name="nc_share_contact_permission">Permission to read contacts is required</string>
|
||||
|
||||
<!-- shared items -->
|
||||
<string name="nc_shared_items">Shared items</string>
|
||||
|
||||
<!-- voice messages -->
|
||||
<string name="nc_voice_message_filename">Talk recording from %1$s (%2$s)</string>
|
||||
<string name="nc_voice_message_hold_to_record_info">Hold to record, release to send.</string>
|
||||
|
@ -498,6 +488,7 @@
|
|||
<string name="nc_dialog_invalid_password">Invalid password</string>
|
||||
<string name="nc_dialog_reauth_or_delete">Do you want to reauthorize or delete this account?</string>
|
||||
|
||||
<!-- Take photo -->
|
||||
<string name="take_photo">Take a photo</string>
|
||||
<string name="take_photo_switch_camera">Switch camera</string>
|
||||
<string name="take_photo_retake_photo">Re-take photo</string>
|
||||
|
@ -507,12 +498,23 @@
|
|||
<string name="take_photo_send">Send</string>
|
||||
<string name="take_photo_error_deleting_picture">Error taking picture</string>
|
||||
<string name="take_photo_permission">Taking a photo is not possible without permissions</string>
|
||||
|
||||
<!-- Audio selection -->
|
||||
<string name="audio_output_bluetooth">Bluetooth</string>
|
||||
<string name="audio_output_speaker">Speaker</string>
|
||||
<string name="audio_output_phone">Phone</string>
|
||||
<string name="audio_output_dialog_headline">Audio output</string>
|
||||
<string name="audio_output_wired_headset">Wired headset</string>
|
||||
|
||||
<!-- Shared items -->
|
||||
<string name="shared_items_media">Media</string>
|
||||
<string name="shared_items_file">File</string>
|
||||
<string name="shared_items_audio">Audio</string>
|
||||
<string name="shared_items_voice">Voice</string>
|
||||
<string name="shared_items_other">Other</string>
|
||||
|
||||
<string name="title_attachments">Attachments</string>
|
||||
|
||||
<string name="reactions_tab_all">All</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -257,4 +257,10 @@
|
|||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="TextAppearanceTab" parent="TextAppearance.Design.Tab">
|
||||
<item name="textAllCaps">false</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue