Open photos embedded in preview cards in the image viewer (#1966)

* Open photos embedded in preview cards in the internal image viewer instead of opening the browser

* Enable toolbar for single image viewer

* Apply review feedback
This commit is contained in:
Levi Bard 2020-10-22 21:15:46 +02:00 committed by GitHub
parent 53bd081802
commit e0346a8e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 51 additions and 32 deletions

View File

@ -407,7 +407,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
accountAvatarImageView.setOnClickListener { avatarView -> accountAvatarImageView.setOnClickListener { avatarView ->
val intent = ViewMediaActivity.newAvatarIntent(avatarView.context, account.avatar) val intent = ViewMediaActivity.newSingleImageIntent(avatarView.context, account.avatar)
avatarView.transitionName = account.avatar avatarView.transitionName = account.avatar
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, avatarView, account.avatar) val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, avatarView, account.avatar)

View File

@ -46,7 +46,7 @@ import com.bumptech.glide.request.FutureTarget
import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID import com.keylesspalace.tusky.BuildConfig.APPLICATION_ID
import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.fragment.ViewImageFragment import com.keylesspalace.tusky.fragment.ViewImageFragment
import com.keylesspalace.tusky.pager.AvatarImagePagerAdapter import com.keylesspalace.tusky.pager.SingleImagePagerAdapter
import com.keylesspalace.tusky.pager.ImagePagerAdapter import com.keylesspalace.tusky.pager.ImagePagerAdapter
import com.keylesspalace.tusky.util.getTemporaryMediaFilename import com.keylesspalace.tusky.util.getTemporaryMediaFilename
import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.AttachmentViewData
@ -68,7 +68,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
companion object { companion object {
private const val EXTRA_ATTACHMENTS = "attachments" private const val EXTRA_ATTACHMENTS = "attachments"
private const val EXTRA_ATTACHMENT_INDEX = "index" private const val EXTRA_ATTACHMENT_INDEX = "index"
private const val EXTRA_AVATAR_URL = "avatar" private const val EXTRA_SINGLE_IMAGE_URL = "single_image"
private const val TAG = "ViewMediaActivity" private const val TAG = "ViewMediaActivity"
@JvmStatic @JvmStatic
@ -79,9 +79,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
return intent return intent
} }
fun newAvatarIntent(context: Context, url: String): Intent { @JvmStatic
fun newSingleImageIntent(context: Context, url: String): Intent {
val intent = Intent(context, ViewMediaActivity::class.java) val intent = Intent(context, ViewMediaActivity::class.java)
intent.putExtra(EXTRA_AVATAR_URL, url) intent.putExtra(EXTRA_SINGLE_IMAGE_URL, url)
return intent return intent
} }
} }
@ -91,6 +92,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
private var attachments: ArrayList<AttachmentViewData>? = null private var attachments: ArrayList<AttachmentViewData>? = null
private val toolbarVisibilityListeners = mutableListOf<ToolbarVisibilityListener>() private val toolbarVisibilityListeners = mutableListOf<ToolbarVisibilityListener>()
private var imageUrl: String? = null
fun addToolbarVisibilityListener(listener: ToolbarVisibilityListener): Function0<Boolean> { fun addToolbarVisibilityListener(listener: ToolbarVisibilityListener): Function0<Boolean> {
this.toolbarVisibilityListeners.add(listener) this.toolbarVisibilityListeners.add(listener)
@ -117,10 +119,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
ImagePagerAdapter(this, realAttachs, initialPosition) ImagePagerAdapter(this, realAttachs, initialPosition)
} else { } else {
val avatarUrl = intent.getStringExtra(EXTRA_AVATAR_URL) imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL)
?: throw IllegalArgumentException("attachment list or avatar url has to be set") ?: throw IllegalArgumentException("attachment list or image url has to be set")
AvatarImagePagerAdapter(this, avatarUrl) SingleImagePagerAdapter(this, imageUrl!!)
} }
viewPager.adapter = adapter viewPager.adapter = adapter
@ -161,11 +163,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
if (attachments != null) { menuInflater.inflate(R.menu.view_media_toolbar, menu)
menuInflater.inflate(R.menu.view_media_toolbar, menu) // We don't support 'open status' from single image views
return true menu?.findItem(R.id.action_open_status)?.isVisible = (attachments != null)
} return true
return false
} }
override fun onPrepareOptionsMenu(menu: Menu?): Boolean { override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
@ -213,7 +214,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
} }
private fun downloadMedia() { private fun downloadMedia() {
val url = attachments!![viewPager.currentItem].attachment.url val url = imageUrl ?: attachments!![viewPager.currentItem].attachment.url
val filename = Uri.parse(url).lastPathSegment val filename = Uri.parse(url).lastPathSegment
Toast.makeText(applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, resources.getString(R.string.download_image, filename), Toast.LENGTH_SHORT).show()
@ -240,8 +241,9 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
} }
private fun copyLink() { private fun copyLink() {
val url = imageUrl ?: attachments!![viewPager.currentItem].attachment.url
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText(null, attachments!![viewPager.currentItem].attachment.url)) clipboard.setPrimaryClip(ClipData.newPlainText(null, url))
} }
private fun shareMedia() { private fun shareMedia() {
@ -251,13 +253,17 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
return return
} }
val attachment = attachments!![viewPager.currentItem].attachment if (imageUrl != null) {
when (attachment.type) { shareImage(directory, imageUrl!!)
Attachment.Type.IMAGE -> shareImage(directory, attachment.url) } else {
Attachment.Type.AUDIO, val attachment = attachments!![viewPager.currentItem].attachment
Attachment.Type.VIDEO, when (attachment.type) {
Attachment.Type.GIFV -> shareMediaFile(directory, attachment.url) Attachment.Type.IMAGE -> shareImage(directory, attachment.url)
else -> Log.e(TAG, "Unknown media format for sharing.") Attachment.Type.AUDIO,
Attachment.Type.VIDEO,
Attachment.Type.GIFV -> shareMediaFile(directory, attachment.url)
else -> Log.e(TAG, "Unknown media format for sharing.")
}
} }
} }

View File

@ -31,6 +31,7 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners; import com.bumptech.glide.load.resource.bitmap.GranularRoundedCorners;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.ViewMediaActivity;
import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Attachment.Focus; import com.keylesspalace.tusky.entity.Attachment.Focus;
import com.keylesspalace.tusky.entity.Attachment.MetaData; import com.keylesspalace.tusky.entity.Attachment.MetaData;
@ -1069,7 +1070,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
cardImage.setImageResource(R.drawable.card_image_placeholder); cardImage.setImageResource(R.drawable.card_image_placeholder);
} }
cardView.setOnClickListener(v -> LinkHelper.openLink(card.getUrl(), v.getContext())); View.OnClickListener visitLink = v -> LinkHelper.openLink(card.getUrl(), v.getContext());
View.OnClickListener openImage = v -> cardView.getContext().startActivity(ViewMediaActivity.newSingleImageIntent(cardView.getContext(), card.getEmbed_url()));
cardInfo.setOnClickListener(visitLink);
// View embedded photos in our image viewer instead of opening the browser
cardImage.setOnClickListener(card.getType().equals(Card.TYPE_PHOTO) && !TextUtils.isEmpty(card.getEmbed_url()) ?
openImage :
visitLink);
cardView.setClipToOutline(true); cardView.setClipToOutline(true);
} else { } else {
cardView.setVisibility(View.GONE); cardView.setVisibility(View.GONE);

View File

@ -27,7 +27,8 @@ data class Card(
val type: String, val type: String,
val width: Int, val width: Int,
val height: Int, val height: Int,
val blurhash: String? val blurhash: String?,
val embed_url: String?
) { ) {
override fun hashCode(): Int { override fun hashCode(): Int {
@ -42,4 +43,7 @@ data class Card(
return account?.url == this.url return account?.url == this.url
} }
companion object {
const val TYPE_PHOTO = "photo"
}
} }

View File

@ -99,9 +99,9 @@ class ViewImageFragment : ViewMediaFragment() {
url = attachment.url url = attachment.url
description = attachment.description description = attachment.description
} else { } else {
url = arguments.getString(ARG_AVATAR_URL) url = arguments.getString(ARG_SINGLE_IMAGE_URL)
if (url == null) { if (url == null) {
throw IllegalArgumentException("attachment or avatar url has to be set") throw IllegalArgumentException("attachment or image url has to be set")
} }
} }

View File

@ -42,7 +42,7 @@ abstract class ViewMediaFragment : BaseFragment() {
@JvmStatic @JvmStatic
protected val ARG_ATTACHMENT = "attach" protected val ARG_ATTACHMENT = "attach"
@JvmStatic @JvmStatic
protected val ARG_AVATAR_URL = "avatarUrl" protected val ARG_SINGLE_IMAGE_URL = "singleImageUrl"
@JvmStatic @JvmStatic
fun newInstance(attachment: Attachment, shouldStartPostponedTransition: Boolean): ViewMediaFragment { fun newInstance(attachment: Attachment, shouldStartPostponedTransition: Boolean): ViewMediaFragment {
@ -62,10 +62,10 @@ abstract class ViewMediaFragment : BaseFragment() {
} }
@JvmStatic @JvmStatic
fun newAvatarInstance(avatarUrl: String): ViewMediaFragment { fun newSingleImageInstance(imageUrl: String): ViewMediaFragment {
val arguments = Bundle(2) val arguments = Bundle(2)
val fragment = ViewImageFragment() val fragment = ViewImageFragment()
arguments.putString(ARG_AVATAR_URL, avatarUrl) arguments.putString(ARG_SINGLE_IMAGE_URL, imageUrl)
arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, true) arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, true)
fragment.arguments = arguments fragment.arguments = arguments

View File

@ -5,14 +5,14 @@ import androidx.fragment.app.FragmentActivity
import com.keylesspalace.tusky.ViewMediaAdapter import com.keylesspalace.tusky.ViewMediaAdapter
import com.keylesspalace.tusky.fragment.ViewMediaFragment import com.keylesspalace.tusky.fragment.ViewMediaFragment
class AvatarImagePagerAdapter( class SingleImagePagerAdapter(
activity: FragmentActivity, activity: FragmentActivity,
private val avatarUrl: String private val imageUrl: String
) : ViewMediaAdapter(activity) { ) : ViewMediaAdapter(activity) {
override fun createFragment(position: Int): Fragment { override fun createFragment(position: Int): Fragment {
return if (position == 0) { return if (position == 0) {
ViewMediaFragment.newAvatarInstance(avatarUrl) ViewMediaFragment.newSingleImageInstance(imageUrl)
} else { } else {
throw IllegalStateException() throw IllegalStateException()
} }