PixelDroid-App-Android/app/src/main/java/org/pixeldroid/app/posts/StatusViewHolder.kt

757 lines
32 KiB
Kotlin
Raw Normal View History

2021-04-22 11:47:18 +02:00
package org.pixeldroid.app.posts
import android.Manifest
2022-06-09 19:29:26 +02:00
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.graphics.Typeface
2021-05-09 18:42:50 +02:00
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
2022-06-09 19:29:26 +02:00
import android.net.Uri
import android.text.method.LinkMovementMethod
import android.util.Log
import android.view.LayoutInflater
2022-10-23 19:29:31 +02:00
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.widget.*
2022-06-09 19:29:26 +02:00
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
2022-06-09 19:29:26 +02:00
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.RecyclerView
2021-05-09 18:42:50 +02:00
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
2022-06-09 19:29:26 +02:00
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
2022-06-09 19:29:26 +02:00
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.request.target.CustomViewTarget
import com.bumptech.glide.request.transition.Transition
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.snackbar.Snackbar
2022-06-09 19:29:26 +02:00
import com.karumi.dexter.Dexter
import com.karumi.dexter.listener.PermissionDeniedResponse
import com.karumi.dexter.listener.PermissionGrantedResponse
import com.karumi.dexter.listener.single.BasePermissionListener
import kotlinx.coroutines.launch
2021-04-22 11:47:18 +02:00
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.AlbumImageViewBinding
2022-06-09 19:29:26 +02:00
import org.pixeldroid.app.databinding.OpenedAlbumBinding
2021-04-22 11:47:18 +02:00
import org.pixeldroid.app.databinding.PostFragmentBinding
2022-06-09 19:29:26 +02:00
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.openActivity
2021-04-22 11:47:18 +02:00
import org.pixeldroid.app.utils.BlurHashDecoder
import org.pixeldroid.app.utils.api.PixelfedAPI
import org.pixeldroid.app.utils.api.objects.Attachment
import org.pixeldroid.app.utils.api.objects.Status
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.POST_TAG
import org.pixeldroid.app.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import org.pixeldroid.app.utils.setProfileImageFromURL
import retrofit2.HttpException
2022-06-09 19:29:26 +02:00
import java.io.File
import java.io.IOException
import kotlin.math.roundToInt
/**
* View Holder for a [Status] RecyclerView list item.
*/
class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHolder(binding.root) {
private var status: Status? = null
fun bind(status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, isActivity: Boolean = false) {
this.itemView.visibility = View.VISIBLE
this.status = status
2022-07-26 14:21:58 +02:00
val maxImageRatio: Float = status?.media_attachments?.maxOfOrNull {
if (it.meta?.original?.width == null || it.meta.original.height == null) {
1f
} else {
it.meta.original.width.toFloat() / it.meta.original.height.toFloat()
}
2022-07-26 14:21:58 +02:00
} ?: 1f
val (displayWidth, displayHeight) = displayDimensionsInPx
2021-03-03 14:00:44 +01:00
if (displayWidth / maxImageRatio > displayHeight * 3/4f) {
binding.postPager.layoutParams.width = ((displayHeight * 3 / 4f) * maxImageRatio).roundToInt()
binding.postPager.layoutParams.height = (displayHeight * 3 / 4f).toInt()
} else {
binding.postPager.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
binding.postPager.layoutParams.height = (displayWidth / maxImageRatio).toInt()
}
//Setup the post layout
val picRequest = Glide.with(itemView).asDrawable().fitCenter()
val user = db.userDao().getActiveUser()!!
setupPost(picRequest, user.instance_uri, isActivity)
activateButtons(pixelfedAPI, db, lifecycleScope, isActivity)
}
private fun setupPost(
request: RequestBuilder<Drawable>,
domain: String,
2022-06-09 19:29:26 +02:00
isActivity: Boolean,
) {
//Setup username as a button that opens the profile
binding.username.apply {
text = status?.account?.getDisplayName() ?: ""
setTypeface(null, Typeface.BOLD)
2021-01-25 17:34:36 +01:00
setOnClickListener { status?.account?.openProfile(binding.root.context) }
}
binding.usernameDesc.apply {
text = status?.account?.getDisplayName() ?: ""
setTypeface(null, Typeface.BOLD)
}
binding.nlikes.apply {
2021-01-25 17:34:36 +01:00
text = status?.getNLikes(binding.root.context)
setTypeface(null, Typeface.BOLD)
}
binding.nshares.apply {
2021-01-25 17:34:36 +01:00
text = status?.getNShares(binding.root.context)
setTypeface(null, Typeface.BOLD)
}
//Convert the date to a readable string
setTextViewFromISO8601(
status?.created_at!!,
binding.postDate,
isActivity,
binding.root.context
)
binding.postDomain.text = status?.getStatusDomain(domain, binding.postDomain.context)
//Setup images
setProfileImageFromURL(
binding.root,
status?.getProfilePicUrl(),
binding.profilePic
)
binding.profilePic.setOnClickListener { status?.account?.openProfile(binding.root.context) }
//Setup post pic only if there are media attachments
if(!status?.media_attachments.isNullOrEmpty()) {
setupPostPics(binding, request)
} else {
binding.postPager.visibility = View.GONE
2021-03-03 14:00:44 +01:00
binding.postIndicator.visibility = View.GONE
}
}
private fun setupPostPics(
binding: PostFragmentBinding,
request: RequestBuilder<Drawable>,
) {
// Standard layout
2021-03-03 14:00:44 +01:00
binding.postPager.visibility = View.VISIBLE
2021-03-03 14:00:44 +01:00
//Attach the given tabs to the view pager
2022-06-09 19:29:26 +02:00
binding.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList(), status?.sensitive, false)
2022-06-09 19:29:26 +02:00
if((status?.media_attachments?.size ?: 0) > 1) {
2021-03-03 14:00:44 +01:00
binding.postIndicator.setViewPager(binding.postPager)
binding.postIndicator.visibility = View.VISIBLE
} else {
binding.postIndicator.visibility = View.GONE
}
2021-03-03 14:00:44 +01:00
if (status?.sensitive == true) {
setupSensitiveLayout()
} else {
// GONE is the default, but have to set it again because of how RecyclerViews work
binding.sensitiveWarning.visibility = View.GONE
}
}
2021-03-03 14:00:44 +01:00
private fun setupSensitiveLayout() {
// Set dark layout and warning message
binding.sensitiveWarning.visibility = View.VISIBLE
//binding.postPicture.colorFilter = ColorMatrixColorFilter(censorMatrix)
2021-03-03 14:00:44 +01:00
fun uncensorPicture(binding: PostFragmentBinding) {
binding.sensitiveWarning.visibility = View.GONE
(binding.postPager.adapter as AlbumViewPagerAdapter).uncensor()
}
binding.sensitiveWarning.setOnClickListener {
uncensorPicture(binding)
}
}
2020-12-29 22:14:32 +01:00
private fun setDescription(
apiHolder: PixelfedAPIHolder,
lifecycleScope: LifecycleCoroutineScope,
2020-12-29 22:14:32 +01:00
) {
2021-01-13 22:46:00 +01:00
binding.description.apply {
if (status?.content.isNullOrBlank()) {
visibility = View.GONE
} else {
2020-12-29 22:14:32 +01:00
text = parseHTMLText(
status?.content.orEmpty(),
status?.mentions,
apiHolder,
binding.root.context,
lifecycleScope
2020-12-29 22:14:32 +01:00
)
movementMethod = LinkMovementMethod.getInstance()
}
}
}
//region buttons
private fun activateButtons(
apiHolder: PixelfedAPIHolder,
db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope,
2022-06-09 19:29:26 +02:00
isActivity: Boolean,
){
//Set the special HTML text
setDescription(apiHolder, lifecycleScope)
//Activate onclickListeners
activateLiker(
apiHolder, status?.favourited ?: false, lifecycleScope
)
activateReblogger(
apiHolder, status?.reblogged ?: false, lifecycleScope
)
if(isActivity){
binding.commenter.visibility = View.INVISIBLE
}
else {
binding.commenter.setOnClickListener {
lifecycleScope.launchWhenCreated {
//Open status in activity
val intent = Intent(it.context, PostActivity::class.java)
intent.putExtra(POST_TAG, status)
intent.putExtra(POST_COMMENT_TAG, true)
it.context.startActivity(intent)
}
}
}
showComments(lifecycleScope, isActivity)
activateMoreButton(apiHolder, db, lifecycleScope)
}
private fun activateReblogger(
apiHolder: PixelfedAPIHolder,
isReblogged: Boolean,
lifecycleScope: LifecycleCoroutineScope,
) {
binding.reblogger.apply {
//Set initial button state
isChecked = isReblogged
//Activate the button
setEventListener { _, buttonState ->
2020-12-30 18:13:39 +01:00
lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
2020-12-30 18:13:39 +01:00
if (buttonState) {
// Button is active
undoReblogPost(api)
2020-12-30 18:13:39 +01:00
} else {
// Button is inactive
reblogPost(api)
2020-12-30 18:13:39 +01:00
}
}
//show animation or not?
true
}
}
}
private suspend fun reblogPost(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
2020-12-30 18:13:39 +01:00
try {
val resp = api.reblogStatus(it)
2020-12-30 18:13:39 +01:00
//Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context)
binding.reblogger.isChecked = resp.reblogged!!
2020-12-30 18:13:39 +01:00
} catch (exception: HttpException) {
Log.e("RESPONSE_CODE", exception.code().toString())
binding.reblogger.isChecked = false
} catch (exception: IOException) {
Log.e("REBLOG ERROR", exception.toString())
binding.reblogger.isChecked = false
2020-12-30 18:13:39 +01:00
}
}
}
private suspend fun undoReblogPost(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
2020-12-30 18:13:39 +01:00
try {
val resp = api.undoReblogStatus(it)
2020-12-30 18:13:39 +01:00
//Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context)
binding.reblogger.isChecked = resp.reblogged!!
2020-12-30 18:13:39 +01:00
} catch (exception: HttpException) {
Log.e("RESPONSE_CODE", exception.code().toString())
binding.reblogger.isChecked = true
} catch (exception: IOException) {
Log.e("REBLOG ERROR", exception.toString())
binding.reblogger.isChecked = true
2020-12-30 18:13:39 +01:00
}
}
}
2022-10-23 19:29:31 +02:00
private suspend fun bookmarkPost(api: PixelfedAPI, db: AppDatabase, menu: Menu, bookmarked: Boolean) : Boolean? {
2022-10-12 15:00:46 +02:00
//Call the api function
status?.id?.let { id ->
try {
2022-10-23 19:29:31 +02:00
if(bookmarked) {
api.bookmarkStatus(id)
} else {
api.undoBookmarkStatus(id)
}
val user = db.userDao().getActiveUser()!!
db.homePostDao().bookmarkStatus(id, user.user_id, user.instance_uri, bookmarked)
db.publicPostDao().bookmarkStatus(id, user.user_id, user.instance_uri, bookmarked)
menu.setGroupVisible(R.id.post_more_menu_group_bookmark, !bookmarked)
menu.setGroupVisible(R.id.post_more_menu_group_unbookmark, bookmarked)
return bookmarked
2022-10-12 15:00:46 +02:00
} catch (exception: HttpException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.bookmark_post_failed_error, exception.code()),
Toast.LENGTH_SHORT
).show()
} catch (exception: IOException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.bookmark_post_failed_io_except),
Toast.LENGTH_SHORT
).show()
}
}
2022-10-23 19:29:31 +02:00
return null
2022-10-12 15:00:46 +02:00
}
private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
2022-10-23 19:29:31 +02:00
var bookmarked: Boolean? = null
binding.statusMore.setOnClickListener {
PopupMenu(it.context, it).apply {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.post_more_menu_report -> {
val intent = Intent(it.context, ReportActivity::class.java)
intent.putExtra(Status.POST_TAG, status)
ContextCompat.startActivity(it.context, intent, null)
true
}
R.id.post_more_menu_share_link -> {
val share = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, status?.uri)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, status?.content)
}, null)
ContextCompat.startActivity(it.context, share, null)
true
}
2022-10-12 15:00:46 +02:00
R.id.post_more_menu_bookmark -> {
lifecycleScope.launch {
2022-10-23 19:29:31 +02:00
bookmarked = bookmarkPost(apiHolder.api ?: apiHolder.setToCurrentUser(), db, menu, true)
2022-10-12 15:00:46 +02:00
}
true
}
R.id.post_more_menu_unbookmark -> {
lifecycleScope.launch {
2022-10-23 19:29:31 +02:00
bookmarked = bookmarkPost(apiHolder.api ?: apiHolder.setToCurrentUser(), db, menu, false)
2022-10-12 15:00:46 +02:00
}
true
}
R.id.post_more_menu_save_to_gallery -> {
Dexter.withContext(binding.root.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object : BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.write_permission_download_pic),
Toast.LENGTH_SHORT
).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
status?.downloadImage(
binding.root.context,
status?.media_attachments?.getOrNull(binding.postPager.currentItem)?.url
?: "",
binding.root
)
}
}).check()
true
}
R.id.post_more_menu_share_picture -> {
Dexter.withContext(binding.root.context)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object : BasePermissionListener() {
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.write_permission_share_pic),
Toast.LENGTH_SHORT
).show()
}
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
status?.downloadImage(
binding.root.context,
status?.media_attachments?.getOrNull(binding.postPager.currentItem)?.url
?: "",
binding.root,
share = true,
)
}
}).check()
true
}
R.id.post_more_menu_delete -> {
val builder = AlertDialog.Builder(binding.root.context)
builder.apply {
setMessage(R.string.delete_dialog)
setPositiveButton(android.R.string.ok) { _, _ ->
lifecycleScope.launch {
val user = db.userDao().getActiveUser()!!
status?.id?.let { id ->
db.homePostDao().delete(id, user.user_id, user.instance_uri)
db.publicPostDao().delete(id, user.user_id, user.instance_uri)
try {
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
api.deleteStatus(id)
binding.root.visibility = View.GONE
} catch (exception: HttpException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.delete_post_failed_error, exception.code()),
Toast.LENGTH_SHORT
).show()
} catch (exception: IOException) {
Toast.makeText(
binding.root.context,
binding.root.context.getString(R.string.delete_post_failed_io_except),
Toast.LENGTH_SHORT
).show()
}
}
}
}
setNegativeButton(android.R.string.cancel) { _, _ -> }
show()
}
true
}
else -> false
}
}
inflate(R.menu.post_more_menu)
2022-10-23 19:29:31 +02:00
if(bookmarked == true || status?.bookmarked == true) {
2022-10-12 15:00:46 +02:00
menu.setGroupVisible(R.id.post_more_menu_group_bookmark, false)
2022-10-23 19:29:31 +02:00
} else if(bookmarked == false || status?.bookmarked != true) {
2022-10-12 15:00:46 +02:00
menu.setGroupVisible(R.id.post_more_menu_group_unbookmark, false)
}
if(status?.media_attachments.isNullOrEmpty()) {
//make sure to disable image-related things if there aren't any
menu.setGroupVisible(R.id.post_more_group_picture, false)
}
if(status?.account?.id == db.userDao().getActiveUser()!!.user_id){
2020-12-19 21:13:30 +01:00
// Enable deleting post if it's the user's
menu.setGroupVisible(R.id.post_more_menu_group_delete, true)
2020-12-19 21:13:30 +01:00
// And disable reporting your own post (just delete it if you don't like it :P)
menu.setGroupVisible(R.id.post_more_menu_group_report, false)
}
show()
}
}
}
private fun activateLiker(
apiHolder: PixelfedAPIHolder,
isLiked: Boolean,
lifecycleScope: LifecycleCoroutineScope,
) {
binding.liker.apply {
//Set initial state
isChecked = isLiked
//Activate the liker
setEventListener { _, buttonState ->
2020-12-30 18:13:39 +01:00
lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
2020-12-30 18:13:39 +01:00
if (buttonState) {
// Button is active, unlike
unLikePostCall(api)
2020-12-30 18:13:39 +01:00
} else {
// Button is inactive, like
likePostCall(api)
2020-12-30 18:13:39 +01:00
}
}
//show animation or not?
true
}
}
2020-12-30 18:13:39 +01:00
2022-06-09 19:29:26 +02:00
//Activate tap interactions (double and single)
2021-03-26 10:26:00 +01:00
binding.postPagerHost.doubleTapCallback = {
lifecycleScope.launchWhenCreated {
//Check that the post isn't hidden
if (binding.sensitiveWarning.visibility == View.GONE) {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setToCurrentUser()
if (binding.liker.isChecked) {
// Button is active, unlike
binding.liker.isChecked = false
unLikePostCall(api)
} else {
// Button is inactive, like
binding.liker.playAnimation()
binding.liker.isChecked = true
binding.likeAnimation.animateView()
likePostCall(api)
2020-12-30 18:13:39 +01:00
}
}
}
}
2022-06-09 19:29:26 +02:00
status?.media_attachments?.let { binding.postPagerHost.images = ArrayList(it) }
}
2021-05-09 18:42:50 +02:00
private fun ImageView.animateView() {
visibility = View.VISIBLE
when (val drawable = drawable) {
is AnimatedVectorDrawableCompat -> {
drawable.start()
}
is AnimatedVectorDrawable -> {
drawable.start()
}
}
}
private suspend fun likePostCall(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
2020-12-30 18:13:39 +01:00
try {
val resp = api.likePost(it)
2020-12-30 18:13:39 +01:00
//Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context)
binding.liker.isChecked = resp.favourited ?: false
2020-12-30 18:13:39 +01:00
} catch (exception: IOException) {
Log.e("LIKE ERROR", exception.toString())
binding.liker.isChecked = false
2020-12-30 18:13:39 +01:00
} catch (exception: HttpException) {
Log.e("RESPONSE_CODE", exception.code().toString())
binding.liker.isChecked = false
2020-12-30 18:13:39 +01:00
}
}
}
private suspend fun unLikePostCall(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
2020-12-30 18:13:39 +01:00
try {
val resp = api.unlikePost(it)
2020-12-30 18:13:39 +01:00
//Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context)
binding.liker.isChecked = resp.favourited ?: false
2020-12-30 18:13:39 +01:00
} catch (exception: IOException) {
Log.e("UNLIKE ERROR", exception.toString())
binding.liker.isChecked = true
2020-12-30 18:13:39 +01:00
} catch (exception: HttpException) {
Log.e("RESPONSE_CODE", exception.code().toString())
binding.liker.isChecked = true
2020-12-30 18:13:39 +01:00
}
}
}
//endregion
private fun showComments(
2022-06-09 19:29:26 +02:00
lifecycleScope: LifecycleCoroutineScope,
isActivity: Boolean,
) {
//Show number of comments on the post
if (status?.replies_count == 0) {
binding.viewComments.text = binding.root.context.getString(R.string.NoCommentsToShow)
} else {
binding.viewComments.apply {
2021-01-25 11:52:27 +01:00
text = resources.getQuantityString(R.plurals.number_comments,
status?.replies_count ?: 0,
status?.replies_count ?: 0
)
if(!isActivity) {
setOnClickListener {
lifecycleScope.launchWhenCreated {
//Open status in activity
val intent = Intent(context, PostActivity::class.java)
intent.putExtra(POST_TAG, status)
intent.putExtra(VIEW_COMMENTS_TAG, true)
context.startActivity(intent)
}
2020-12-30 18:13:39 +01:00
}
}
}
}
}
2020-12-29 22:35:45 +01:00
companion object {
fun create(parent: ViewGroup): StatusViewHolder {
val itemBinding = PostFragmentBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return StatusViewHolder(itemBinding)
}
}
}
2022-06-09 19:29:26 +02:00
class AlbumViewPagerAdapter(
private val media_attachments: List<Attachment>, private var sensitive: Boolean?,
private val opened: Boolean,
2022-06-09 19:29:26 +02:00
) :
RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
2022-06-09 19:29:26 +02:00
private var isActionBarHidden: Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
2022-06-09 19:29:26 +02:00
return if(!opened) ViewHolderClosed(AlbumImageViewBinding.inflate(
LayoutInflater.from(parent.context), parent, false
2022-06-09 19:29:26 +02:00
)) else ViewHolderOpen(OpenedAlbumBinding.inflate(
LayoutInflater.from(parent.context), parent, false
))
}
override fun getItemCount() = media_attachments.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
2021-03-03 14:00:44 +01:00
media_attachments[position].apply {
2022-02-10 18:24:44 +01:00
val video = type == Attachment.AttachmentType.video
2021-03-03 14:00:44 +01:00
val blurhashBitMap = blurhash?.let {
BlurHashDecoder.blurHashBitmap(
holder.binding.root.resources,
it,
meta?.original?.width,
meta?.original?.height
2021-02-08 01:32:10 +01:00
)
2021-03-03 14:00:44 +01:00
}
if (sensitive == false) {
2022-02-10 18:24:44 +01:00
val imageUrl = if(video) preview_url else url
2022-06-09 19:29:26 +02:00
if(opened){
Glide.with(holder.binding.root)
.download(GlideUrl(imageUrl))
.into(object : CustomViewTarget<SubsamplingScaleImageView, File>((holder.image as SubsamplingScaleImageView)) {
override fun onResourceReady(resource: File, t: Transition<in File>?) =
view.setImage(ImageSource.uri(Uri.fromFile(resource)))
override fun onLoadFailed(errorDrawable: Drawable?) {}
override fun onResourceCleared(placeholder: Drawable?) {}
})
(holder.image as SubsamplingScaleImageView).apply {
setMinimumDpi(80)
setDoubleTapZoomDpi(240)
resetScaleAndCenter()
}
holder.image.setOnClickListener {
val windowInsetsController = WindowCompat.getInsetsController((it.context as Activity).window, it)
// Configure the behavior of the hidden system bars
if (isActionBarHidden) {
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide both the status bar and the navigation bar
(it.context as AppCompatActivity).supportActionBar?.show()
windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
isActionBarHidden = false
} else {
// Configure the behavior of the hidden system bars
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide both the status bar and the navigation bar
(it.context as AppCompatActivity).supportActionBar?.hide()
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
isActionBarHidden = true
}
}
}
else Glide.with(holder.binding.root)
2021-03-03 14:00:44 +01:00
.asDrawable().fitCenter()
.placeholder(blurhashBitMap)
2022-06-09 19:29:26 +02:00
.load(imageUrl).into(holder.image as ImageView)
} else if(!opened){
2021-03-03 14:00:44 +01:00
Glide.with(holder.binding.root)
.asDrawable().fitCenter()
2022-06-09 19:29:26 +02:00
.load(blurhashBitMap).into(holder.image as ImageView)
2021-03-03 14:00:44 +01:00
}
2022-02-10 18:24:44 +01:00
holder.videoPlayButton.visibility = if(video) View.VISIBLE else View.GONE
2022-06-09 19:29:26 +02:00
if(video && (opened || media_attachments.size == 1)){
holder.videoPlayButton.setOnClickListener {
openActivity(holder.binding.root.context, url, description)
}
holder.image.setOnClickListener {
openActivity(holder.binding.root.context, url, description)
2022-02-10 18:24:44 +01:00
}
}
2021-03-03 14:00:44 +01:00
val description = description
.orEmpty()
.ifEmpty { holder.binding.root.context.getString(R.string.no_description) }
2021-03-03 14:00:44 +01:00
holder.image.setOnLongClickListener {
Snackbar.make(it, description, Snackbar.LENGTH_SHORT).show()
true
}
holder.image.contentDescription = description
}
2021-03-03 14:00:44 +01:00
}
2021-03-03 14:00:44 +01:00
fun uncensor(){
sensitive = false
notifyDataSetChanged()
}
2022-06-09 19:29:26 +02:00
abstract class ViewHolder(open val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root){
abstract val image: View
abstract val videoPlayButton: ImageView
}
2022-06-09 19:29:26 +02:00
class ViewHolderOpen(override val binding: OpenedAlbumBinding) : ViewHolder(binding) {
override val image: SubsamplingScaleImageView = binding.imageImageView
override val videoPlayButton: ImageView = binding.videoPlayButton
}
class ViewHolderClosed(override val binding: AlbumImageViewBinding) : ViewHolder(binding) {
override val image: ImageView = binding.imageImageView
override val videoPlayButton: ImageView = binding.videoPlayButton
}
}