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.android.gms:play-services-base:18.0.1'
|
||||||
gplayImplementation "com.google.firebase:firebase-messaging:23.0.0"
|
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") {
|
task installGitHooks(type: Copy, group: "development") {
|
||||||
|
|
|
@ -96,6 +96,11 @@
|
||||||
android:name="android.max_aspect"
|
android:name="android.max_aspect"
|
||||||
android:value="10" />
|
android:value="10" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.SharedItemsActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/AppTheme"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
android:label="@string/nc_app_name"
|
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;
|
package com.nextcloud.talk.adapters.messages;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.LayerDrawable;
|
import android.graphics.drawable.LayerDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -43,44 +41,31 @@ import android.widget.PopupMenu;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import com.nextcloud.talk.R;
|
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.application.NextcloudTalkApplication;
|
||||||
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
|
||||||
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
|
||||||
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation;
|
||||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
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.database.UserEntity;
|
||||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet;
|
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.DisplayUtils;
|
||||||
import com.nextcloud.talk.utils.DrawableUtils;
|
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 com.stfalcon.chatkit.messages.MessageHolders;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper;
|
import androidx.appcompat.view.ContextThemeWrapper;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.emoji.widget.EmojiTextView;
|
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 autodagger.AutoInjector;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.SingleObserver;
|
import io.reactivex.SingleObserver;
|
||||||
|
@ -114,6 +99,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
|
|
||||||
ReactionsInsideMessageBinding reactionsBinding;
|
ReactionsInsideMessageBinding reactionsBinding;
|
||||||
|
|
||||||
|
FileViewerUtils fileViewerUtils;
|
||||||
|
|
||||||
View clickView;
|
View clickView;
|
||||||
|
|
||||||
ReactionsInterface reactionsInterface;
|
ReactionsInterface reactionsInterface;
|
||||||
|
@ -138,7 +125,7 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
} else {
|
} else {
|
||||||
userAvatar.setVisibility(View.VISIBLE);
|
userAvatar.setVisibility(View.VISIBLE);
|
||||||
userAvatar.setOnClickListener(v -> {
|
userAvatar.setOnClickListener(v -> {
|
||||||
if (payload instanceof ProfileBottomSheet){
|
if (payload instanceof ProfileBottomSheet) {
|
||||||
((ProfileBottomSheet) payload).showFor(message.actorId, v.getContext());
|
((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) {
|
if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||||
|
|
||||||
|
fileViewerUtils = new FileViewerUtils(context, message.activeUser);
|
||||||
|
|
||||||
String fileName = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
String fileName = message.getSelectedIndividualHashMap().get(KEY_NAME);
|
||||||
getMessageText().setText(fileName);
|
getMessageText().setText(fileName);
|
||||||
if (message.getSelectedIndividualHashMap().containsKey(KEY_CONTACT_NAME)) {
|
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)) {
|
if (message.getSelectedIndividualHashMap().containsKey(KEY_CONTACT_PHOTO)) {
|
||||||
image = getPreviewContactPhoto();
|
image = getPreviewContactPhoto();
|
||||||
Drawable drawable = getDrawableFromContactDetails(
|
Drawable drawable = getDrawableFromContactDetails(
|
||||||
context,
|
context,
|
||||||
message.getSelectedIndividualHashMap().get(KEY_CONTACT_PHOTO));
|
message.getSelectedIndividualHashMap().get(KEY_CONTACT_PHOTO));
|
||||||
image.getHierarchy().setPlaceholderImage(drawable);
|
image.getHierarchy().setPlaceholderImage(drawable);
|
||||||
} else if (message.getSelectedIndividualHashMap().containsKey(KEY_MIMETYPE)) {
|
} else if (message.getSelectedIndividualHashMap().containsKey(KEY_MIMETYPE)) {
|
||||||
String mimetype = message.getSelectedIndividualHashMap().get(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);
|
fetchFileInformation("/" + message.getSelectedIndividualHashMap().get(KEY_PATH), message.activeUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null){
|
if (message.activeUser != null && message.activeUser.getUsername() != null && message.activeUser.getBaseUrl() != null) {
|
||||||
String accountString =
|
|
||||||
message.activeUser.getUsername() + "@" +
|
|
||||||
message.activeUser.getBaseUrl()
|
|
||||||
.replace("https://", "")
|
|
||||||
.replace("http://", "");
|
|
||||||
|
|
||||||
clickView.setOnClickListener(v -> {
|
clickView.setOnClickListener(v -> {
|
||||||
String mimetype = message.getSelectedIndividualHashMap().get(KEY_MIMETYPE);
|
fileViewerUtils.openFile(message, progressBar, getMessageText(), image);
|
||||||
if (isSupportedForInternalViewer(mimetype) || canBeHandledByExternalApp(mimetype, fileName)) {
|
|
||||||
openOrDownloadFile(message);
|
|
||||||
} else {
|
|
||||||
openFileInFilesApp(message, accountString);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
clickView.setOnLongClickListener(l -> {
|
clickView.setOnLongClickListener(l -> {
|
||||||
onMessageViewLongClick(message, accountString);
|
onMessageViewLongClick(message);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "failed to set click listener because activeUser, username or baseUrl were null");
|
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) {
|
} else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
|
||||||
getMessageText().setText("GIPHY");
|
getMessageText().setText("GIPHY");
|
||||||
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", getMessageText());
|
DisplayUtils.setClickableString("GIPHY", "https://giphy.com", getMessageText());
|
||||||
|
@ -273,9 +237,9 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
Drawable drawable = null;
|
Drawable drawable = null;
|
||||||
if (!base64.equals("")) {
|
if (!base64.equals("")) {
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(
|
||||||
Base64.decode(base64.getBytes(), Base64.DEFAULT));
|
Base64.decode(base64.getBytes(), Base64.DEFAULT));
|
||||||
drawable = Drawable.createFromResourceStream(context.getResources(),
|
drawable = Drawable.createFromResourceStream(context.getResources(),
|
||||||
null, inputStream, null, null);
|
null, inputStream, null, null);
|
||||||
try {
|
try {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -287,151 +251,8 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract EmojiTextView getMessageText();
|
private void onMessageViewLongClick(ChatMessage message) {
|
||||||
|
if (fileViewerUtils.isSupportedForInternalViewer(message.getSelectedIndividualHashMap().get(KEY_MIMETYPE))) {
|
||||||
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))) {
|
|
||||||
previewMessageInterface.onPreviewMessageLongClick(message);
|
previewMessageInterface.onPreviewMessageLongClick(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -452,132 +273,17 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
popupMenu.inflate(R.menu.chat_preview_message_menu);
|
popupMenu.inflate(R.menu.chat_preview_message_menu);
|
||||||
|
|
||||||
popupMenu.setOnMenuItemClickListener(item -> {
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
popupMenu.show();
|
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) {
|
private void fetchFileInformation(String url, UserEntity activeUser) {
|
||||||
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
Single.fromCallable(new Callable<ReadFilesystemOperation>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -585,34 +291,34 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
return new ReadFilesystemOperation(okHttpClient, activeUser, url, 0);
|
return new ReadFilesystemOperation(okHttpClient, activeUser, url, 0);
|
||||||
}
|
}
|
||||||
}).observeOn(Schedulers.io())
|
}).observeOn(Schedulers.io())
|
||||||
.subscribe(new SingleObserver<ReadFilesystemOperation>() {
|
.subscribe(new SingleObserver<ReadFilesystemOperation>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSubscribe(@NonNull Disposable d) {
|
public void onSubscribe(@NonNull Disposable d) {
|
||||||
// unused atm
|
// unused atm
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
|
public void onSuccess(@NonNull ReadFilesystemOperation readFilesystemOperation) {
|
||||||
DavResponse davResponse = readFilesystemOperation.readRemotePath();
|
DavResponse davResponse = readFilesystemOperation.readRemotePath();
|
||||||
if (davResponse.data != null) {
|
if (davResponse.data != null) {
|
||||||
List<BrowserFile> browserFileList = (List<BrowserFile>) davResponse.data;
|
List<BrowserFile> browserFileList = (List<BrowserFile>) davResponse.data;
|
||||||
if (!browserFileList.isEmpty()) {
|
if (!browserFileList.isEmpty()) {
|
||||||
new Handler(context.getMainLooper()).post(() -> {
|
new Handler(context.getMainLooper()).post(() -> {
|
||||||
int resourceId = DrawableUtils
|
int resourceId = DrawableUtils
|
||||||
.INSTANCE
|
.INSTANCE
|
||||||
.getDrawableResourceIdForMimeType(browserFileList.get(0).mimeType);
|
.getDrawableResourceIdForMimeType(browserFileList.get(0).mimeType);
|
||||||
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
|
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
|
||||||
image.getHierarchy().setPlaceholderImage(drawable);
|
image.getHierarchy().setPlaceholderImage(drawable);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(@NonNull Throwable e) {
|
public void onError(@NonNull Throwable e) {
|
||||||
Log.e(TAG, "Error reading file information", e);
|
Log.e(TAG, "Error reading file information", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assignReactionInterface(ReactionsInterface reactionsInterface) {
|
public void assignReactionInterface(ReactionsInterface reactionsInterface) {
|
||||||
|
@ -622,4 +328,22 @@ public abstract class MagicPreviewMessageViewHolder extends MessageHolders.Incom
|
||||||
public void assignPreviewMessageInterface(PreviewMessageInterface previewMessageInterface) {
|
public void assignPreviewMessageInterface(PreviewMessageInterface previewMessageInterface) {
|
||||||
this.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.capabilities.CapabilitiesOverall;
|
||||||
import com.nextcloud.talk.models.json.chat.ChatOverall;
|
import com.nextcloud.talk.models.json.chat.ChatOverall;
|
||||||
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
|
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.RoomOverall;
|
||||||
import com.nextcloud.talk.models.json.conversations.RoomsOverall;
|
import com.nextcloud.talk.models.json.conversations.RoomsOverall;
|
||||||
import com.nextcloud.talk.models.json.generic.GenericOverall;
|
import com.nextcloud.talk.models.json.generic.GenericOverall;
|
||||||
|
@ -338,6 +339,12 @@ public interface NcApi {
|
||||||
@Field("actorDisplayName") String actorDisplayName,
|
@Field("actorDisplayName") String actorDisplayName,
|
||||||
@Field("replyTo") Integer replyTo);
|
@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
|
@GET
|
||||||
Observable<MentionOverall> getMentionAutocompleteSuggestions(@Header("Authorization") String authorization,
|
Observable<MentionOverall> getMentionAutocompleteSuggestions(@Header("Authorization") String authorization,
|
||||||
@Url String url, @Query("search") String query,
|
@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.Build.VERSION_CODES.O
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.os.Parcelable
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.os.VibrationEffect
|
import android.os.VibrationEffect
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
|
@ -99,6 +100,7 @@ import com.nextcloud.talk.BuildConfig
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.activities.CallActivity
|
import com.nextcloud.talk.activities.CallActivity
|
||||||
import com.nextcloud.talk.activities.MainActivity
|
import com.nextcloud.talk.activities.MainActivity
|
||||||
|
import com.nextcloud.talk.activities.SharedItemsActivity
|
||||||
import com.nextcloud.talk.activities.TakePhotoActivity
|
import com.nextcloud.talk.activities.TakePhotoActivity
|
||||||
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
|
import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
|
||||||
import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
|
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.UriUtils
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
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_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_ID
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
|
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY
|
||||||
|
@ -188,9 +191,7 @@ import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -253,6 +254,7 @@ class ChatController(args: Bundle) :
|
||||||
var conversationInfoMenuItem: MenuItem? = null
|
var conversationInfoMenuItem: MenuItem? = null
|
||||||
var conversationVoiceCallMenuItem: MenuItem? = null
|
var conversationVoiceCallMenuItem: MenuItem? = null
|
||||||
var conversationVideoMenuItem: MenuItem? = null
|
var conversationVideoMenuItem: MenuItem? = null
|
||||||
|
var conversationSharedItemsItem: MenuItem? = null
|
||||||
|
|
||||||
var magicWebSocketInstance: MagicWebSocketInstance? = null
|
var magicWebSocketInstance: MagicWebSocketInstance? = null
|
||||||
|
|
||||||
|
@ -1464,7 +1466,7 @@ class ChatController(args: Bundle) :
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
|
bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
|
||||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
|
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(
|
router.pushController(
|
||||||
RouterTransaction.with(BrowserForSharingController(bundle))
|
RouterTransaction.with(BrowserForSharingController(bundle))
|
||||||
.pushChangeHandler(VerticalChangeHandler())
|
.pushChangeHandler(VerticalChangeHandler())
|
||||||
|
@ -1476,7 +1478,7 @@ class ChatController(args: Bundle) :
|
||||||
Log.d(TAG, "showShareLocationScreen")
|
Log.d(TAG, "showShareLocationScreen")
|
||||||
|
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||||
router.pushController(
|
router.pushController(
|
||||||
RouterTransaction.with(LocationPickerController(bundle))
|
RouterTransaction.with(LocationPickerController(bundle))
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
.pushChangeHandler(HorizontalChangeHandler())
|
||||||
|
@ -1487,7 +1489,7 @@ class ChatController(args: Bundle) :
|
||||||
private fun showConversationInfoScreen() {
|
private fun showConversationInfoScreen() {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
|
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())
|
bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, inOneToOneCall())
|
||||||
router.pushController(
|
router.pushController(
|
||||||
RouterTransaction.with(ConversationInfoController(bundle))
|
RouterTransaction.with(ConversationInfoController(bundle))
|
||||||
|
@ -2299,6 +2301,7 @@ class ChatController(args: Bundle) :
|
||||||
conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
|
conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
|
||||||
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
|
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
|
||||||
conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
|
conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
|
||||||
|
conversationSharedItemsItem = menu.findItem(R.id.shared_items)
|
||||||
|
|
||||||
loadAvatarForStatusBar()
|
loadAvatarForStatusBar()
|
||||||
}
|
}
|
||||||
|
@ -2337,10 +2340,22 @@ class ChatController(args: Bundle) :
|
||||||
showConversationInfoScreen()
|
showConversationInfoScreen()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.shared_items -> {
|
||||||
|
showSharedItems()
|
||||||
|
return true
|
||||||
|
}
|
||||||
else -> return super.onOptionsItemSelected(item)
|
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> {
|
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||||
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
||||||
val chatMessageIterator = chatMessageMap.iterator()
|
val chatMessageIterator = chatMessageMap.iterator()
|
||||||
|
@ -2402,7 +2417,7 @@ class ChatController(args: Bundle) :
|
||||||
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
|
bundle.putParcelable(KEY_USER_ENTITY, conversationUser)
|
||||||
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
|
bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword)
|
||||||
bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl)
|
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) {
|
if (isVoiceOnlyCall) {
|
||||||
bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true)
|
bundle.putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true)
|
||||||
|
|
|
@ -27,9 +27,11 @@
|
||||||
package com.nextcloud.talk.controllers
|
package com.nextcloud.talk.controllers
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
@ -49,6 +51,7 @@ import com.bluelinelabs.conductor.RouterTransaction
|
||||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.activities.SharedItemsActivity
|
||||||
import com.nextcloud.talk.adapters.items.ParticipantItem
|
import com.nextcloud.talk.adapters.items.ParticipantItem
|
||||||
import com.nextcloud.talk.api.NcApi
|
import com.nextcloud.talk.api.NcApi
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
|
@ -88,11 +91,8 @@ import io.reactivex.schedulers.Schedulers
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Comparator
|
|
||||||
import java.util.HashMap
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -175,10 +175,19 @@ class ConversationInfoController(args: Bundle) :
|
||||||
binding.leaveConversationAction.setOnClickListener { leaveConversation() }
|
binding.leaveConversationAction.setOnClickListener { leaveConversation() }
|
||||||
binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog(null) }
|
binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog(null) }
|
||||||
binding.addParticipantsAction.setOnClickListener { addParticipants() }
|
binding.addParticipantsAction.setOnClickListener { addParticipants() }
|
||||||
|
binding.showSharedItemsAction.setOnClickListener { showSharedItems() }
|
||||||
|
|
||||||
fetchRoomInfo()
|
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) {
|
override fun onViewBound(view: View) {
|
||||||
super.onViewBound(view)
|
super.onViewBound(view)
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,9 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||||
if (MessageDigest.isEqual(
|
if (MessageDigest.isEqual(
|
||||||
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
Objects.requireNonNull(individualHashMap.get("type")).getBytes(Charsets.UTF_8),
|
||||||
("file").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;
|
selectedIndividualHashMap = individualHashMap;
|
||||||
if (!isVoiceMessage()) {
|
if (!isVoiceMessage()) {
|
||||||
if (getActiveUser() != null && getActiveUser().getBaseUrl() != null) {
|
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) {
|
public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
|
||||||
return getUrlForChat(version, baseUrl, token) + "/" + 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) {
|
public static String getUrlForSignaling(int version, String baseUrl) {
|
||||||
return getUrlForApi(version, baseUrl) + "/signaling";
|
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:id="@+id/participants_list_category"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@+id/settings"
|
android:layout_below="@+id/category_shared_items"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
apc:cardBackgroundColor="@color/bg_default"
|
apc:cardBackgroundColor="@color/bg_default"
|
||||||
apc:cardElevation="0dp"
|
apc:cardElevation="0dp"
|
||||||
|
@ -213,6 +213,29 @@
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</LinearLayout>
|
</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>
|
</RelativeLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -37,8 +37,13 @@
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/conversation_info"
|
android:id="@+id/conversation_info"
|
||||||
android:icon="@drawable/ic_info_white_24dp"
|
|
||||||
android:orderInCategory="1"
|
android:orderInCategory="1"
|
||||||
android:title="@string/nc_conversation_menu_conversation_info"
|
android:title="@string/nc_conversation_menu_conversation_info"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/shared_items"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
android:title="Shared Items"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
<dimen name="avatar_size">40dp</dimen>
|
<dimen name="avatar_size">40dp</dimen>
|
||||||
<dimen name="avatar_size_app_bar">30dp</dimen>
|
<dimen name="avatar_size_app_bar">30dp</dimen>
|
||||||
<dimen name="avatar_size_big">96dp</dimen>
|
<dimen name="avatar_size_big">96dp</dimen>
|
||||||
|
<dimen name="file_icon_size">40dp</dimen>
|
||||||
|
|
||||||
<dimen name="chat_text_size">14sp</dimen>
|
<dimen name="chat_text_size">14sp</dimen>
|
||||||
<dimen name="message_bubble_corners_radius">6dp</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_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_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_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_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_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>
|
<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_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_key" translatable="false">screen_lock</string>
|
||||||
<string name="nc_settings_screen_lock_timeout_title">Screen lock inactivity timeout</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_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_title">Screen security</string>
|
||||||
<string name="nc_settings_screen_security_desc">Prevents screenshots in the recent list and inside the app</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_incoming">INCOMING</string>
|
||||||
<string name="nc_call_ringing">RINGING</string>
|
<string name="nc_call_ringing">RINGING</string>
|
||||||
<string name="nc_connecting_call">Connecting…</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_nick_guest">Guest</string>
|
||||||
<string name="nc_public_call">New public conversation</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>
|
<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 -->
|
<!-- Empty states -->
|
||||||
<string name="nc_conversations_empty">Join a conversation or start a new one</string>
|
<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_conversations_empty_details">Say hi to your friends and colleagues!</string>
|
||||||
<string name="nc_hello">Hello</string>
|
|
||||||
|
|
||||||
<!-- Other -->
|
<!-- Other -->
|
||||||
<string name="nc_limit_hit">%s characters limit has been hit</string>
|
<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_lobby_start_soon">The meeting will start soon</string>
|
||||||
<string name="nc_manual">Not set</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_allow_guests">Allow guests</string>
|
||||||
<string name="nc_last_moderator_title">Could not leave conversation</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>
|
<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">Share contact</string>
|
||||||
<string name="nc_share_contact_permission">Permission to read contacts is required</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 -->
|
<!-- voice messages -->
|
||||||
<string name="nc_voice_message_filename">Talk recording from %1$s (%2$s)</string>
|
<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>
|
<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_invalid_password">Invalid password</string>
|
||||||
<string name="nc_dialog_reauth_or_delete">Do you want to reauthorize or delete this account?</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">Take a photo</string>
|
||||||
<string name="take_photo_switch_camera">Switch camera</string>
|
<string name="take_photo_switch_camera">Switch camera</string>
|
||||||
<string name="take_photo_retake_photo">Re-take photo</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_send">Send</string>
|
||||||
<string name="take_photo_error_deleting_picture">Error taking picture</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>
|
<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_bluetooth">Bluetooth</string>
|
||||||
<string name="audio_output_speaker">Speaker</string>
|
<string name="audio_output_speaker">Speaker</string>
|
||||||
<string name="audio_output_phone">Phone</string>
|
<string name="audio_output_phone">Phone</string>
|
||||||
<string name="audio_output_dialog_headline">Audio output</string>
|
<string name="audio_output_dialog_headline">Audio output</string>
|
||||||
<string name="audio_output_wired_headset">Wired headset</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>
|
<string name="reactions_tab_all">All</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -257,4 +257,10 @@
|
||||||
<item name="android:textStyle">bold</item>
|
<item name="android:textStyle">bold</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TextAppearanceTab" parent="TextAppearance.Design.Tab">
|
||||||
|
<item name="textAllCaps">false</item>
|
||||||
|
<item name="android:textAllCaps">false</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue