Open album in full screen

This commit is contained in:
Matthieu 2022-06-09 19:29:26 +02:00
parent 52cefe63aa
commit 4806dc4a06
11 changed files with 222 additions and 257 deletions

View File

@ -142,6 +142,8 @@ dependencies {
// CameraX View class // CameraX View class
implementation "androidx.camera:camera-view:$cameraX_version" implementation "androidx.camera:camera-view:$cameraX_version"
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
def room_version = "2.4.2" def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"

View File

@ -6,7 +6,6 @@ import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.openLinkWithText
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
@ -24,9 +23,6 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.TestRule
import org.junit.rules.Timeout
import org.junit.runner.Description
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.model.Statement import org.junit.runners.model.Statement
@ -68,22 +64,22 @@ class HomeFeedTest {
@RepeatTest @RepeatTest
fun clickingTabOnAlbumShowsNextPhoto() { fun clickingTabOnAlbumShowsNextPhoto() {
//Wait for the feed to load //Wait for the feed to load
waitForView(R.id.postPager) waitForView(R.id.albumPager)
activityScenario.onActivity { activityScenario.onActivity {
a -> run { a -> run {
//Pick the second photo //Pick the second photo
a.findViewById<ViewPager2>(R.id.postPager).currentItem = 2 a.findViewById<ViewPager2>(R.id.albumPager).currentItem = 2
} }
} }
onView(first(withId(R.id.postPager))).check(matches(isDisplayed())) onView(first(withId(R.id.albumPager))).check(matches(isDisplayed()))
} }
@Test @Test
@RepeatTest @RepeatTest
fun tabReClickScrollUp() { fun tabReClickScrollUp() {
//Wait for the feed to load //Wait for the feed to load
waitForView(R.id.postPager) waitForView(R.id.albumPager)
onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(4)) onView(withId(R.id.list)).perform(scrollToPosition<StatusViewHolder>(4))
@ -97,7 +93,7 @@ class HomeFeedTest {
@RepeatTest @RepeatTest
fun hashtag() { fun hashtag() {
//Wait for the feed to load //Wait for the feed to load
waitForView(R.id.postPager) waitForView(R.id.albumPager)
onView(allOf(withClassName(endsWith("RecyclerView")), not(withId(R.id.material_drawer_recycler_view)))) onView(allOf(withClassName(endsWith("RecyclerView")), not(withId(R.id.material_drawer_recycler_view))))
.perform( .perform(

View File

@ -23,9 +23,13 @@
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:replace="android:allowBackup"> tools:replace="android:allowBackup">
<activity <activity
android:name=".posts.MediaViewerActivity" android:name=".posts.AlbumActivity"
android:exported="false" android:exported="false"
android:theme="@style/AppTheme.ActionBar.Transparent"/>
<activity
android:name=".posts.MediaViewerActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="false"
android:theme="@style/AppTheme.NoActionBar" /> android:theme="@style/AppTheme.NoActionBar" />
<activity android:name=".postCreation.camera.CameraActivity" /> <activity android:name=".postCreation.camera.CameraActivity" />
<activity <activity

View File

@ -0,0 +1,38 @@
package org.pixeldroid.app.posts
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import org.pixeldroid.app.databinding.ActivityAlbumBinding
import org.pixeldroid.app.utils.BaseActivity
import org.pixeldroid.app.utils.api.objects.Attachment
class AlbumActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityAlbumBinding.inflate(layoutInflater)
setContentView(binding.root)
val mediaAttachments = intent.getSerializableExtra("images") as ArrayList<Attachment>
binding.albumPager.adapter = AlbumViewPagerAdapter(mediaAttachments,
sensitive = false,
opened = true)
if(mediaAttachments.size == 1){
binding.albumPager.isUserInputEnabled = false
}
else if((mediaAttachments.size) > 1) {
binding.postIndicator.setViewPager(binding.albumPager)
binding.postIndicator.visibility = View.VISIBLE
} else {
binding.postIndicator.visibility = View.GONE
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setBackgroundDrawable(null)
window.statusBarColor = ContextCompat.getColor(this,android.R.color.transparent);
}
}

View File

@ -7,6 +7,7 @@ import android.media.AudioManager.STREAM_MUSIC
import android.os.Bundle import android.os.Bundle
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat.getInsetsController
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.media.AudioAttributesCompat import androidx.media.AudioAttributesCompat
@ -50,19 +51,19 @@ class MediaViewerActivity : BaseActivity() {
mediaPlayer.setMediaItem(mediaItem) mediaPlayer.setMediaItem(mediaItem)
binding.videoView.mediaControlView?.setOnFullScreenListener{ view, fullscreen -> binding.videoView.mediaControlView?.setOnFullScreenListener{ view, fullscreen ->
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView) val windowInsetsController = getInsetsController(window, window.decorView)
if (!fullscreen) { if (!fullscreen) {
// Configure the behavior of the hidden system bars // Configure the behavior of the hidden system bars
windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide both the status bar and the navigation bar // Hide both the status bar and the navigation bar
windowInsetsController?.show(WindowInsetsCompat.Type.systemBars()) windowInsetsController.show(WindowInsetsCompat.Type.systemBars())
supportActionBar?.show() supportActionBar?.show()
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
} else { } else {
// Configure the behavior of the hidden system bars // Configure the behavior of the hidden system bars
windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// Hide both the status bar and the navigation bar // Hide both the status bar and the navigation bar
windowInsetsController?.hide(WindowInsetsCompat.Type.systemBars()) windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
requestedOrientation = requestedOrientation =
if (mediaPlayer.videoSize.height < mediaPlayer.videoSize.width) { if (mediaPlayer.videoSize.height < mediaPlayer.videoSize.width) {

View File

@ -1,32 +1,17 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
package org.pixeldroid.app.posts package org.pixeldroid.app.posts
import android.content.Context import android.content.Context
import android.content.Intent
import android.util.AttributeSet import android.util.AttributeSet
import android.view.GestureDetector import android.view.GestureDetector
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewConfiguration import android.view.ViewConfiguration
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import org.pixeldroid.app.utils.api.objects.Attachment
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.sign import kotlin.math.sign
@ -53,6 +38,7 @@ class NestedScrollableHost(context: Context, attrs: AttributeSet? = null) :
} }
var images: ArrayList<Attachment> = ArrayList();
var doubleTapCallback: (() -> Unit)? = null var doubleTapCallback: (() -> Unit)? = null
private val child: View? get() = if (childCount > 0) getChildAt(0) else null private val child: View? get() = if (childCount > 0) getChildAt(0) else null
@ -96,8 +82,16 @@ class NestedScrollableHost(context: Context, attrs: AttributeSet? = null) :
} }
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
//TODO open image full screen // Disable opening AlbumActivity if the only image is a video (let the video open directly)
Toast.makeText(this@NestedScrollableHost.context, "yay you did it", Toast.LENGTH_SHORT).show() if(images.size == 1 && images.first().type == Attachment.AttachmentType.video){
return super.onSingleTapConfirmed(e)
}
val intent = Intent(context, AlbumActivity::class.java)
intent.putExtra("images", images)
context.startActivity(intent)
return super.onSingleTapConfirmed(e) return super.onSingleTapConfirmed(e)
} }
override fun onScroll( override fun onScroll(

View File

@ -1,27 +1,46 @@
package org.pixeldroid.app.posts package org.pixeldroid.app.posts
import android.Manifest import android.Manifest
import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.method.LinkMovementMethod import android.text.method.LinkMovementMethod
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
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 import com.google.android.material.snackbar.Snackbar
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
import org.pixeldroid.app.R import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.AlbumImageViewBinding import org.pixeldroid.app.databinding.AlbumImageViewBinding
import org.pixeldroid.app.databinding.OpenedAlbumBinding
import org.pixeldroid.app.databinding.PostFragmentBinding import org.pixeldroid.app.databinding.PostFragmentBinding
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.openActivity
import org.pixeldroid.app.utils.BlurHashDecoder import org.pixeldroid.app.utils.BlurHashDecoder
import org.pixeldroid.app.utils.ImageConverter import org.pixeldroid.app.utils.ImageConverter
import org.pixeldroid.app.utils.api.PixelfedAPI import org.pixeldroid.app.utils.api.PixelfedAPI
@ -32,15 +51,8 @@ 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.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.di.PixelfedAPIHolder import org.pixeldroid.app.utils.di.PixelfedAPIHolder
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
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.VIDEO_DESCRIPTION_TAG
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.VIDEO_URL_TAG
import org.pixeldroid.app.posts.MediaViewerActivity.Companion.openActivity
import retrofit2.HttpException import retrofit2.HttpException
import java.io.File
import java.io.IOException import java.io.IOException
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -88,7 +100,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
private fun setupPost( private fun setupPost(
request: RequestBuilder<Drawable>, request: RequestBuilder<Drawable>,
domain: String, domain: String,
isActivity: Boolean isActivity: Boolean,
) { ) {
//Setup username as a button that opens the profile //Setup username as a button that opens the profile
binding.username.apply { binding.username.apply {
@ -148,9 +160,9 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
binding.postPager.visibility = View.VISIBLE binding.postPager.visibility = View.VISIBLE
//Attach the given tabs to the view pager //Attach the given tabs to the view pager
binding.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList(), status?.sensitive) binding.postPager.adapter = AlbumViewPagerAdapter(status?.media_attachments ?: emptyList(), status?.sensitive, false)
if(status?.media_attachments?.size ?: 0 > 1) { if((status?.media_attachments?.size ?: 0) > 1) {
binding.postIndicator.setViewPager(binding.postPager) binding.postIndicator.setViewPager(binding.postPager)
binding.postIndicator.visibility = View.VISIBLE binding.postIndicator.visibility = View.VISIBLE
} else { } else {
@ -206,7 +218,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
apiHolder: PixelfedAPIHolder, apiHolder: PixelfedAPIHolder,
db: AppDatabase, db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope, lifecycleScope: LifecycleCoroutineScope,
isActivity: Boolean isActivity: Boolean,
){ ){
//Set the special HTML text //Set the special HTML text
setDescription(apiHolder, lifecycleScope) setDescription(apiHolder, lifecycleScope)
@ -459,7 +471,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
//Activate double tap liking //Activate tap interactions (double and single)
binding.postPagerHost.doubleTapCallback = { binding.postPagerHost.doubleTapCallback = {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
//Check that the post isn't hidden //Check that the post isn't hidden
@ -479,6 +491,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
} }
status?.media_attachments?.let { binding.postPagerHost.images = ArrayList(it) }
} }
private fun ImageView.animateView() { private fun ImageView.animateView() {
visibility = View.VISIBLE visibility = View.VISIBLE
@ -534,8 +548,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//endregion //endregion
private fun showComments( private fun showComments(
lifecycleScope: LifecycleCoroutineScope, lifecycleScope: LifecycleCoroutineScope,
isActivity: Boolean isActivity: Boolean,
) { ) {
//Show number of comments on the post //Show number of comments on the post
if (status?.replies_count == 0) { if (status?.replies_count == 0) {
@ -574,14 +588,20 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
} }
} }
private class AlbumViewPagerAdapter(private val media_attachments: List<Attachment>, private var sensitive: Boolean?) : class AlbumViewPagerAdapter(
private val media_attachments: List<Attachment>, private var sensitive: Boolean?,
private val opened: Boolean, //TODO if opened don't open again, and use PhotoView instead of shite
) :
RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() { RecyclerView.Adapter<AlbumViewPagerAdapter.ViewHolder>() {
private var isActionBarHidden: Boolean = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemBinding = AlbumImageViewBinding.inflate( return if(!opened) ViewHolderClosed(AlbumImageViewBinding.inflate(
LayoutInflater.from(parent.context), parent, false LayoutInflater.from(parent.context), parent, false
) )) else ViewHolderOpen(OpenedAlbumBinding.inflate(
return ViewHolder(itemBinding) LayoutInflater.from(parent.context), parent, false
))
} }
override fun getItemCount() = media_attachments.size override fun getItemCount() = media_attachments.size
@ -598,19 +618,53 @@ private class AlbumViewPagerAdapter(private val media_attachments: List<Attachme
} }
if (sensitive == false) { if (sensitive == false) {
val imageUrl = if(video) preview_url else url val imageUrl = if(video) preview_url else url
Glide.with(holder.binding.root) 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)
.asDrawable().fitCenter() .asDrawable().fitCenter()
.placeholder(blurhashBitMap) .placeholder(blurhashBitMap)
.load(imageUrl).into(holder.image) .load(imageUrl).into(holder.image as ImageView)
} else { } else if(!opened){
Glide.with(holder.binding.root) Glide.with(holder.binding.root)
.asDrawable().fitCenter() .asDrawable().fitCenter()
.load(blurhashBitMap).into(holder.image) .load(blurhashBitMap).into(holder.image as ImageView)
} }
holder.videoPlayButton.visibility = if(video) View.VISIBLE else View.GONE holder.videoPlayButton.visibility = if(video) View.VISIBLE else View.GONE
if(video){ if(video && (opened || media_attachments.size == 1)){
holder.videoPlayButton.setOnClickListener { holder.videoPlayButton.setOnClickListener {
openActivity(holder.binding.root.context, url, description) openActivity(holder.binding.root.context, url, description)
} }
@ -636,9 +690,17 @@ private class AlbumViewPagerAdapter(private val media_attachments: List<Attachme
sensitive = false sensitive = false
notifyDataSetChanged() notifyDataSetChanged()
} }
abstract class ViewHolder(open val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root){
abstract val image: View
abstract val videoPlayButton: ImageView
}
class ViewHolder(val binding: AlbumImageViewBinding) : RecyclerView.ViewHolder(binding.root){ class ViewHolderOpen(override val binding: OpenedAlbumBinding) : ViewHolder(binding) {
val image: ImageView = binding.imageImageView override val image: SubsamplingScaleImageView = binding.imageImageView
val videoPlayButton: ImageView = binding.videoPlayButton 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
} }
} }

View File

@ -1,196 +0,0 @@
package org.pixeldroid.app.posts
import android.content.Context
import android.graphics.Matrix
import androidx.appcompat.widget.AppCompatImageView
import android.graphics.PointF
import android.view.ScaleGestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
import android.util.AttributeSet
import android.util.Log
import kotlin.math.abs
import kotlin.math.min
// See https://stackoverflow.com/a/29030243
class TouchImageView(context: Context, attrs: AttributeSet? = null) :
AppCompatImageView(context, attrs) {
var touchMatrix: Matrix? = Matrix()
var mode = NONE
// Remember some things for zooming
var last = PointF()
var start = PointF()
var minScale = 1f
var maxScale = 3f
var m: FloatArray = FloatArray(9)
var viewWidth = 0
var viewHeight = 0
var saveScale = 1f
private var origWidth = 0f
private var origHeight = 0f
var oldMeasuredWidth = 0
var oldMeasuredHeight = 0
var mScaleDetector: ScaleGestureDetector? = null
init {
super.setClickable(true)
mScaleDetector = ScaleGestureDetector(context, ScaleListener())
imageMatrix = touchMatrix
scaleType = ScaleType.MATRIX
setOnTouchListener { _, event ->
mScaleDetector!!.onTouchEvent(event)
val curr = PointF(event.x, event.y)
when (event.action) {
MotionEvent.ACTION_MOVE -> if (mode == DRAG) {
val deltaX = curr.x - last.x
val deltaY = curr.y - last.y
val fixTransX =
getFixDragTrans(deltaX, viewWidth.toFloat(), origWidth * saveScale)
val fixTransY =
getFixDragTrans(deltaY, viewHeight.toFloat(), origHeight * saveScale)
touchMatrix!!.postTranslate(fixTransX, fixTransY)
fixTrans()
last[curr.x] = curr.y
val transX = m[Matrix.MTRANS_X]
if ((getFixTrans(transX,
viewWidth.toFloat(),
origWidth * saveScale) + fixTransX).toInt() == 0
) startInterceptEvent() else stopInterceptEvent()
}
MotionEvent.ACTION_DOWN -> {
last.set(curr)
start.set(last)
mode = DRAG
stopInterceptEvent()
}
MotionEvent.ACTION_UP -> {
mode = NONE
val xDiff = abs(curr.x - start.x).toInt()
val yDiff = abs(curr.y - start.y).toInt()
if (xDiff < CLICK && yDiff < CLICK) performClick()
startInterceptEvent()
}
MotionEvent.ACTION_POINTER_UP -> mode = NONE
}
imageMatrix = touchMatrix
invalidate()
true // indicate event was handled
}
}
private fun startInterceptEvent() {
parent.requestDisallowInterceptTouchEvent(false)
}
private fun stopInterceptEvent() {
parent.requestDisallowInterceptTouchEvent(true)
}
fun setMaxZoom(x: Float) {
maxScale = x
}
private inner class ScaleListener : SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
mode = ZOOM
return true
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
var mScaleFactor = detector.scaleFactor
val origScale = saveScale
saveScale *= mScaleFactor
if (saveScale > maxScale) {
saveScale = maxScale
mScaleFactor = maxScale / origScale
} else if (saveScale < minScale) {
saveScale = minScale
mScaleFactor = minScale / origScale
}
if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight) touchMatrix!!.postScale(
mScaleFactor,
mScaleFactor,
(viewWidth / 2).toFloat(),
(viewHeight / 2).toFloat()) else touchMatrix!!.postScale(mScaleFactor,
mScaleFactor,
detector.focusX,
detector.focusY)
fixTrans()
return true
}
}
fun fixTrans() {
touchMatrix!!.getValues(m)
val transX = m[Matrix.MTRANS_X]
val transY = m[Matrix.MTRANS_Y]
val fixTransX = getFixTrans(transX, viewWidth.toFloat(), origWidth * saveScale)
val fixTransY = getFixTrans(transY, viewHeight.toFloat(), origHeight * saveScale)
if (fixTransX != 0f || fixTransY != 0f) touchMatrix!!.postTranslate(fixTransX, fixTransY)
}
fun getFixTrans(trans: Float, viewSize: Float, contentSize: Float): Float {
val minTrans: Float
val maxTrans: Float
if (contentSize <= viewSize) {
minTrans = 0f
maxTrans = viewSize - contentSize
} else {
minTrans = viewSize - contentSize
maxTrans = 0f
}
if (trans < minTrans) return -trans + minTrans
return if (trans > maxTrans) -trans + maxTrans else 0f
}
fun getFixDragTrans(delta: Float, viewSize: Float, contentSize: Float): Float {
return if (contentSize <= viewSize) {
0f
} else delta
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
viewWidth = MeasureSpec.getSize(widthMeasureSpec)
viewHeight = MeasureSpec.getSize(heightMeasureSpec)
// Rescales image on rotation
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight || viewWidth == 0 || viewHeight == 0) return
oldMeasuredHeight = viewHeight
oldMeasuredWidth = viewWidth
if (saveScale == 1f) {
//Fit to screen.
val scale: Float
val drawable = drawable
if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) return
val bmWidth = drawable.intrinsicWidth
val bmHeight = drawable.intrinsicHeight
Log.d("bmSize", "bmWidth: $bmWidth bmHeight : $bmHeight")
val scaleX = viewWidth.toFloat() / bmWidth.toFloat()
val scaleY = viewHeight.toFloat() / bmHeight.toFloat()
scale = min(scaleX, scaleY)
touchMatrix!!.setScale(scale, scale)
// Center the image
var redundantYSpace = viewHeight.toFloat() - scale * bmHeight.toFloat()
var redundantXSpace = viewWidth.toFloat() - scale * bmWidth.toFloat()
redundantYSpace /= 2f
redundantXSpace /= 2f
touchMatrix!!.postTranslate(redundantXSpace, redundantYSpace)
origWidth = viewWidth - 2 * redundantXSpace
origHeight = viewHeight - 2 * redundantYSpace
imageMatrix = touchMatrix
}
fixTrans()
}
companion object {
// We can be in one of these 3 states
const val NONE = 0
const val DRAG = 1
const val ZOOM = 2
const val CLICK = 3
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/albumPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:orientation="horizontal" />
<me.relex.circleindicator.CircleIndicator3
android:id="@+id/postIndicator"
android:layout_width="wrap_content"
android:layout_height="32dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
android:id="@+id/imageImageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/videoPlayButton"
android:visibility="gone"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:scaleType="fitCenter"
android:src="@drawable/play_circle_filled"
android:contentDescription="@string/play_video" />
</FrameLayout>

View File

@ -21,6 +21,19 @@
<item name="android:textAppearance">@android:style/TextAppearance.Large</item> <item name="android:textAppearance">@android:style/TextAppearance.Large</item>
</style> </style>
<style name="AppTheme.ActionBar.Transparent" parent="AppTheme">
<item name="colorPrimary">@android:color/transparent</item>
<item name="colorPrimaryDark">@android:color/transparent</item>
<item name="colorPrimaryVariant">@android:color/transparent</item>
<item name="colorAccent">@android:color/transparent</item>
<item name="colorSecondary">@android:color/transparent</item>
<item name="statusBarForeground">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="statusBarBackground">@android:color/transparent</item>
<item name="android:windowTranslucentStatus">true</item>
</style>
<style name="AppTheme.NoActionBar"> <style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>