Open album in full screen
This commit is contained in:
parent
52cefe63aa
commit
4806dc4a06
|
@ -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"
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue