2018-10-01 19:50:17 +10:00
|
|
|
/* Copyright 2017 Andrew Dawson
|
|
|
|
*
|
|
|
|
* This file is a part of Tusky.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Tusky 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 Tusky; if not,
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
|
|
|
|
|
|
|
package com.keylesspalace.tusky
|
|
|
|
|
2018-10-16 04:56:11 +11:00
|
|
|
import android.Manifest
|
2018-10-01 19:50:17 +10:00
|
|
|
import android.animation.Animator
|
|
|
|
import android.animation.AnimatorListenerAdapter
|
2018-10-16 04:56:11 +11:00
|
|
|
import android.app.DownloadManager
|
2018-11-02 01:13:37 +11:00
|
|
|
import android.content.ClipData
|
|
|
|
import android.content.ClipboardManager
|
2018-10-01 19:50:17 +10:00
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.pm.PackageManager
|
|
|
|
import android.graphics.Bitmap
|
|
|
|
import android.graphics.Color
|
|
|
|
import android.net.Uri
|
|
|
|
import android.os.Bundle
|
2018-10-16 04:56:11 +11:00
|
|
|
import android.os.Environment
|
2019-08-05 04:22:57 +10:00
|
|
|
import android.transition.Transition
|
2018-10-01 19:50:17 +10:00
|
|
|
import android.util.Log
|
|
|
|
import android.view.Menu
|
|
|
|
import android.view.MenuItem
|
|
|
|
import android.view.View
|
2018-10-16 04:56:11 +11:00
|
|
|
import android.webkit.MimeTypeMap
|
|
|
|
import android.widget.Toast
|
2019-08-05 04:22:57 +10:00
|
|
|
import androidx.core.content.FileProvider
|
2019-04-17 05:39:12 +10:00
|
|
|
import androidx.lifecycle.Lifecycle
|
2019-08-05 04:22:57 +10:00
|
|
|
import androidx.viewpager.widget.PagerAdapter
|
|
|
|
import androidx.viewpager.widget.ViewPager
|
2019-04-17 05:39:12 +10:00
|
|
|
import com.bumptech.glide.Glide
|
|
|
|
import com.bumptech.glide.request.FutureTarget
|
2018-10-01 19:50:17 +10:00
|
|
|
import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
|
2018-10-16 04:56:11 +11:00
|
|
|
import com.keylesspalace.tusky.entity.Attachment
|
|
|
|
import com.keylesspalace.tusky.fragment.ViewImageFragment
|
2018-10-01 19:50:17 +10:00
|
|
|
import com.keylesspalace.tusky.pager.AvatarImagePagerAdapter
|
|
|
|
import com.keylesspalace.tusky.pager.ImagePagerAdapter
|
|
|
|
import com.keylesspalace.tusky.util.getTemporaryMediaFilename
|
|
|
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
2019-04-17 05:39:12 +10:00
|
|
|
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
|
|
|
|
import com.uber.autodispose.autoDisposable
|
|
|
|
import io.reactivex.Single
|
|
|
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
|
|
import io.reactivex.schedulers.Schedulers
|
2018-10-01 19:50:17 +10:00
|
|
|
import kotlinx.android.synthetic.main.activity_view_media.*
|
|
|
|
import java.io.File
|
|
|
|
import java.io.FileNotFoundException
|
|
|
|
import java.io.FileOutputStream
|
|
|
|
import java.io.IOException
|
2019-08-05 04:22:57 +10:00
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
2018-10-01 19:50:17 +10:00
|
|
|
|
2018-10-16 04:56:11 +11:00
|
|
|
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener {
|
2018-10-01 19:50:17 +10:00
|
|
|
companion object {
|
|
|
|
private const val EXTRA_ATTACHMENTS = "attachments"
|
|
|
|
private const val EXTRA_ATTACHMENT_INDEX = "index"
|
|
|
|
private const val EXTRA_AVATAR_URL = "avatar"
|
|
|
|
private const val TAG = "ViewMediaActivity"
|
|
|
|
|
|
|
|
@JvmStatic
|
|
|
|
fun newIntent(context: Context?, attachments: List<AttachmentViewData>, index: Int): Intent {
|
|
|
|
val intent = Intent(context, ViewMediaActivity::class.java)
|
|
|
|
intent.putParcelableArrayListExtra(EXTRA_ATTACHMENTS, ArrayList(attachments))
|
|
|
|
intent.putExtra(EXTRA_ATTACHMENT_INDEX, index)
|
|
|
|
return intent
|
|
|
|
}
|
|
|
|
|
|
|
|
fun newAvatarIntent(context: Context, url: String): Intent {
|
|
|
|
val intent = Intent(context, ViewMediaActivity::class.java)
|
|
|
|
intent.putExtra(EXTRA_AVATAR_URL, url)
|
|
|
|
return intent
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-05 04:22:57 +10:00
|
|
|
var isToolbarVisible = true
|
|
|
|
private set
|
2018-10-01 19:50:17 +10:00
|
|
|
|
2019-08-05 04:22:57 +10:00
|
|
|
private var attachments: ArrayList<AttachmentViewData>? = null
|
|
|
|
private val toolbarVisibilityListeners = mutableListOf<ToolbarVisibilityListener>()
|
2018-10-01 19:50:17 +10:00
|
|
|
|
|
|
|
fun addToolbarVisibilityListener(listener: ToolbarVisibilityListener): Function0<Boolean> {
|
|
|
|
this.toolbarVisibilityListeners.add(listener)
|
2019-08-05 04:22:57 +10:00
|
|
|
listener(isToolbarVisible)
|
2018-10-01 19:50:17 +10:00
|
|
|
return { toolbarVisibilityListeners.remove(listener) }
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
setContentView(R.layout.activity_view_media)
|
|
|
|
|
|
|
|
supportPostponeEnterTransition()
|
|
|
|
|
|
|
|
// Gather the parameters.
|
|
|
|
attachments = intent.getParcelableArrayListExtra(EXTRA_ATTACHMENTS)
|
|
|
|
val initialPosition = intent.getIntExtra(EXTRA_ATTACHMENT_INDEX, 0)
|
|
|
|
|
2019-08-05 04:22:57 +10:00
|
|
|
// Adapter is actually of existential type PageAdapter & SharedElementsTransitionListener
|
|
|
|
// but it cannot be expressed and if I don't specify type explicitly compilation fails
|
|
|
|
// (probably a bug in compiler)
|
|
|
|
val adapter: PagerAdapter = if (attachments != null) {
|
2019-04-21 16:24:06 +10:00
|
|
|
val realAttachs = attachments!!.map(AttachmentViewData::attachment)
|
2018-10-01 19:50:17 +10:00
|
|
|
// Setup the view pager.
|
|
|
|
ImagePagerAdapter(supportFragmentManager, realAttachs, initialPosition)
|
|
|
|
|
|
|
|
} else {
|
2019-04-17 05:39:12 +10:00
|
|
|
val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL)
|
|
|
|
?: throw IllegalArgumentException("attachment list or avatar url has to be set")
|
2018-10-01 19:50:17 +10:00
|
|
|
|
|
|
|
AvatarImagePagerAdapter(supportFragmentManager, avatarUrl)
|
|
|
|
}
|
|
|
|
|
|
|
|
viewPager.adapter = adapter
|
|
|
|
viewPager.currentItem = initialPosition
|
2019-04-17 05:39:12 +10:00
|
|
|
viewPager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
|
2018-10-01 19:50:17 +10:00
|
|
|
override fun onPageSelected(position: Int) {
|
|
|
|
toolbar.title = adapter.getPageTitle(position)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// Setup the toolbar.
|
|
|
|
setSupportActionBar(toolbar)
|
|
|
|
val actionBar = supportActionBar
|
|
|
|
if (actionBar != null) {
|
|
|
|
actionBar.setDisplayHomeAsUpEnabled(true)
|
|
|
|
actionBar.setDisplayShowHomeEnabled(true)
|
|
|
|
actionBar.title = adapter.getPageTitle(initialPosition)
|
|
|
|
}
|
2018-11-16 23:31:03 +11:00
|
|
|
toolbar.setNavigationOnClickListener { supportFinishAfterTransition() }
|
2018-10-01 19:50:17 +10:00
|
|
|
toolbar.setOnMenuItemClickListener { item: MenuItem ->
|
|
|
|
when (item.itemId) {
|
2019-03-23 22:49:36 +11:00
|
|
|
R.id.action_download -> requestDownloadMedia()
|
2018-10-01 19:50:17 +10:00
|
|
|
R.id.action_open_status -> onOpenStatus()
|
2018-10-16 04:56:11 +11:00
|
|
|
R.id.action_share_media -> shareMedia()
|
2018-11-02 01:13:37 +11:00
|
|
|
R.id.action_copy_media_link -> copyLink()
|
2018-10-01 19:50:17 +10:00
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
|
|
|
|
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
|
|
|
|
window.statusBarColor = Color.BLACK
|
2019-08-05 04:22:57 +10:00
|
|
|
window.sharedElementEnterTransition.addListener(object : NoopTransitionListener {
|
|
|
|
override fun onTransitionEnd(transition: Transition) {
|
|
|
|
(adapter as SharedElementTransitionListener).onTransitionEnd()
|
|
|
|
window.sharedElementEnterTransition.removeListener(this)
|
|
|
|
}
|
|
|
|
})
|
2018-10-01 19:50:17 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
2019-04-17 05:39:12 +10:00
|
|
|
if (attachments != null) {
|
2018-10-01 19:50:17 +10:00
|
|
|
menuInflater.inflate(R.menu.view_media_toolbar, menu)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-04-17 05:39:12 +10:00
|
|
|
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
|
|
|
|
menu?.findItem(R.id.action_share_media)?.isEnabled = !isCreating
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-10-01 19:50:17 +10:00
|
|
|
override fun onBringUp() {
|
|
|
|
supportStartPostponedEnterTransition()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDismiss() {
|
|
|
|
supportFinishAfterTransition()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPhotoTap() {
|
2019-08-05 04:22:57 +10:00
|
|
|
isToolbarVisible = !isToolbarVisible
|
2018-10-01 19:50:17 +10:00
|
|
|
for (listener in toolbarVisibilityListeners) {
|
2019-08-05 04:22:57 +10:00
|
|
|
listener(isToolbarVisible)
|
2019-04-17 05:39:12 +10:00
|
|
|
}
|
2019-08-05 04:22:57 +10:00
|
|
|
val visibility = if (isToolbarVisible) View.VISIBLE else View.INVISIBLE
|
|
|
|
val alpha = if (isToolbarVisible) 1.0f else 0.0f
|
2018-10-01 19:50:17 +10:00
|
|
|
|
|
|
|
toolbar.animate().alpha(alpha)
|
2019-04-17 05:39:12 +10:00
|
|
|
.setListener(object : AnimatorListenerAdapter() {
|
2018-10-01 19:50:17 +10:00
|
|
|
override fun onAnimationEnd(animation: Animator) {
|
|
|
|
toolbar.visibility = visibility
|
|
|
|
animation.removeListener(this)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.start()
|
|
|
|
}
|
|
|
|
|
2019-03-23 22:49:36 +11:00
|
|
|
private fun downloadMedia() {
|
|
|
|
val url = attachments!![viewPager.currentItem].attachment.url
|
|
|
|
val filename = Uri.parse(url).lastPathSegment
|
|
|
|
Toast.makeText(applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT).show()
|
|
|
|
|
|
|
|
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
|
|
val request = DownloadManager.Request(Uri.parse(url))
|
|
|
|
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES,
|
|
|
|
getString(R.string.app_name) + "/" + filename)
|
|
|
|
downloadManager.enqueue(request)
|
2018-10-01 19:50:17 +10:00
|
|
|
}
|
|
|
|
|
2019-03-23 22:49:36 +11:00
|
|
|
private fun requestDownloadMedia() {
|
2019-04-22 06:59:49 +10:00
|
|
|
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { _, grantResults ->
|
2019-03-23 22:49:36 +11:00
|
|
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
|
|
downloadMedia()
|
|
|
|
} else {
|
|
|
|
showErrorDialog(toolbar, R.string.error_media_download_permission, R.string.action_retry) { requestDownloadMedia() }
|
|
|
|
}
|
2018-10-16 04:56:11 +11:00
|
|
|
}
|
2018-10-01 19:50:17 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun onOpenStatus() {
|
|
|
|
val attach = attachments!![viewPager.currentItem]
|
|
|
|
startActivityWithSlideInAnimation(ViewThreadActivity.startIntent(this, attach.statusId, attach.statusUrl))
|
|
|
|
}
|
|
|
|
|
2018-11-02 01:13:37 +11:00
|
|
|
private fun copyLink() {
|
|
|
|
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
|
|
|
clipboard.primaryClip = ClipData.newPlainText(null, attachments!![viewPager.currentItem].attachment.url)
|
|
|
|
}
|
|
|
|
|
2018-10-16 04:56:11 +11:00
|
|
|
private fun shareMedia() {
|
2018-10-01 19:50:17 +10:00
|
|
|
val directory = applicationContext.getExternalFilesDir("Tusky")
|
|
|
|
if (directory == null || !(directory.exists())) {
|
|
|
|
Log.e(TAG, "Error obtaining directory to save temporary media.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val attachment = attachments!![viewPager.currentItem].attachment
|
2019-04-17 05:39:12 +10:00
|
|
|
when (attachment.type) {
|
2018-10-16 04:56:11 +11:00
|
|
|
Attachment.Type.IMAGE -> shareImage(directory, attachment.url)
|
|
|
|
Attachment.Type.VIDEO,
|
|
|
|
Attachment.Type.GIFV -> shareVideo(directory, attachment.url)
|
|
|
|
else -> Log.e(TAG, "Unknown media format for sharing.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun shareFile(file: File, mimeType: String?) {
|
|
|
|
val sendIntent = Intent()
|
|
|
|
sendIntent.action = Intent.ACTION_SEND
|
|
|
|
sendIntent.putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(applicationContext, "$APPLICATION_ID.fileprovider", file))
|
|
|
|
sendIntent.type = mimeType
|
|
|
|
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to)))
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-17 05:39:12 +10:00
|
|
|
private var isCreating: Boolean = false
|
|
|
|
|
2018-10-16 04:56:11 +11:00
|
|
|
private fun shareImage(directory: File, url: String) {
|
2019-04-17 05:39:12 +10:00
|
|
|
isCreating = true
|
|
|
|
progressBarShare.visibility = View.VISIBLE
|
|
|
|
invalidateOptionsMenu()
|
2018-10-01 19:50:17 +10:00
|
|
|
val file = File(directory, getTemporaryMediaFilename("png"))
|
2019-04-17 05:39:12 +10:00
|
|
|
val futureTask: FutureTarget<Bitmap> =
|
|
|
|
Glide.with(applicationContext).asBitmap().load(Uri.parse(url)).submit()
|
|
|
|
Single.fromCallable {
|
|
|
|
val bitmap = futureTask.get()
|
|
|
|
try {
|
|
|
|
val stream = FileOutputStream(file)
|
|
|
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
|
|
|
stream.close()
|
|
|
|
return@fromCallable true
|
|
|
|
} catch (fnfe: FileNotFoundException) {
|
|
|
|
Log.e(TAG, "Error writing temporary media.")
|
|
|
|
} catch (ioe: IOException) {
|
|
|
|
Log.e(TAG, "Error writing temporary media.")
|
2018-10-01 19:50:17 +10:00
|
|
|
}
|
2019-04-17 05:39:12 +10:00
|
|
|
return@fromCallable false
|
2018-10-01 19:50:17 +10:00
|
|
|
|
2019-04-17 05:39:12 +10:00
|
|
|
}
|
2018-10-01 19:50:17 +10:00
|
|
|
|
2019-04-17 05:39:12 +10:00
|
|
|
.subscribeOn(Schedulers.io())
|
|
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
|
|
.doOnDispose {
|
|
|
|
futureTask.cancel(true)
|
|
|
|
}
|
|
|
|
.autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
|
|
|
.subscribe(
|
|
|
|
{ result ->
|
|
|
|
Log.d(TAG, "Download image result: $result")
|
|
|
|
isCreating = false
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
progressBarShare.visibility = View.GONE
|
|
|
|
if (result)
|
|
|
|
shareFile(file, "image/png")
|
|
|
|
},
|
|
|
|
{ error ->
|
|
|
|
isCreating = false
|
|
|
|
invalidateOptionsMenu()
|
|
|
|
progressBarShare.visibility = View.GONE
|
|
|
|
Log.e(TAG, "Failed to download image", error)
|
|
|
|
}
|
|
|
|
)
|
2018-10-01 19:50:17 +10:00
|
|
|
|
2018-10-16 04:56:11 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun shareVideo(directory: File, url: String) {
|
|
|
|
val uri = Uri.parse(url)
|
|
|
|
val mimeTypeMap = MimeTypeMap.getSingleton()
|
|
|
|
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
|
|
|
|
val mimeType = mimeTypeMap.getMimeTypeFromExtension(extension)
|
|
|
|
val filename = getTemporaryMediaFilename(extension)
|
|
|
|
val file = File(directory, filename)
|
|
|
|
|
|
|
|
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
|
|
|
val request = DownloadManager.Request(uri)
|
|
|
|
request.setDestinationUri(Uri.fromFile(file))
|
|
|
|
request.setVisibleInDownloadsUi(false)
|
|
|
|
downloadManager.enqueue(request)
|
|
|
|
|
|
|
|
shareFile(file, mimeType)
|
2018-10-01 19:50:17 +10:00
|
|
|
}
|
|
|
|
}
|
2019-08-05 04:22:57 +10:00
|
|
|
|
|
|
|
interface SharedElementTransitionListener {
|
|
|
|
fun onTransitionEnd()
|
|
|
|
}
|
|
|
|
|
|
|
|
interface NoopTransitionListener : Transition.TransitionListener {
|
|
|
|
override fun onTransitionEnd(transition: Transition) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onTransitionResume(transition: Transition) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onTransitionPause(transition: Transition) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onTransitionCancel(transition: Transition) {
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onTransitionStart(transition: Transition) {
|
|
|
|
}
|
|
|
|
}
|