Merge remote-tracking branch 'tuskyapp/develop'
This commit is contained in:
commit
48d1b05bde
|
@ -172,7 +172,7 @@ dependencies {
|
||||||
|
|
||||||
implementation "com.github.connyduck:sparkbutton:4.0.0"
|
implementation "com.github.connyduck:sparkbutton:4.0.0"
|
||||||
|
|
||||||
implementation "com.github.chrisbanes:PhotoView:2.3.0"
|
implementation 'com.github.MikeOrtiz:TouchImageView:3.0.1'
|
||||||
|
|
||||||
implementation "com.mikepenz:materialdrawer:$materialdrawerVersion"
|
implementation "com.mikepenz:materialdrawer:$materialdrawerVersion"
|
||||||
implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion"
|
implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion"
|
||||||
|
|
|
@ -81,6 +81,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
||||||
private var blocking: Boolean = false
|
private var blocking: Boolean = false
|
||||||
private var muting: Boolean = false
|
private var muting: Boolean = false
|
||||||
|
private var blockingDomain: Boolean = false
|
||||||
private var showingReblogs: Boolean = false
|
private var showingReblogs: Boolean = false
|
||||||
private var loadedAccount: Account? = null
|
private var loadedAccount: Account? = null
|
||||||
|
|
||||||
|
@ -528,6 +529,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
}
|
}
|
||||||
blocking = relation.blocking
|
blocking = relation.blocking
|
||||||
muting = relation.muting
|
muting = relation.muting
|
||||||
|
blockingDomain = relation.blockingDomain
|
||||||
showingReblogs = relation.showingReblogs
|
showingReblogs = relation.showingReblogs
|
||||||
|
|
||||||
accountFollowsYouTextView.visible(relation.followedBy)
|
accountFollowsYouTextView.visible(relation.followedBy)
|
||||||
|
@ -626,7 +628,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
// If we can't get the domain, there's no way we can mute it anyway...
|
// If we can't get the domain, there's no way we can mute it anyway...
|
||||||
menu.removeItem(R.id.action_mute_domain)
|
menu.removeItem(R.id.action_mute_domain)
|
||||||
} else {
|
} else {
|
||||||
muteDomain.title = getString(R.string.action_mute_domain, domain)
|
if (blockingDomain) {
|
||||||
|
muteDomain.title = getString(R.string.action_unmute_domain, domain)
|
||||||
|
} else {
|
||||||
|
muteDomain.title = getString(R.string.action_mute_domain, domain)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,12 +677,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showMuteDomainWarningDialog(instance: String) {
|
private fun toggleBlockDomain(instance: String) {
|
||||||
AlertDialog.Builder(this)
|
if(blockingDomain) {
|
||||||
.setMessage(getString(R.string.mute_domain_warning, instance))
|
viewModel.unblockDomain(instance)
|
||||||
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.muteDomain(instance) }
|
} else {
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
AlertDialog.Builder(this)
|
||||||
.show()
|
.setMessage(getString(R.string.mute_domain_warning, instance))
|
||||||
|
.setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) }
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleBlock() {
|
private fun toggleBlock() {
|
||||||
|
@ -757,7 +767,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_mute_domain -> {
|
R.id.action_mute_domain -> {
|
||||||
showMuteDomainWarningDialog(domain)
|
toggleBlockDomain(domain)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.action_show_reblogs -> {
|
R.id.action_show_reblogs -> {
|
||||||
|
|
|
@ -33,9 +33,9 @@ import at.connyduck.sparkbutton.helpers.Utils
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.github.chrisbanes.photoview.PhotoView
|
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.util.withLifecycleContext
|
import com.keylesspalace.tusky.util.withLifecycleContext
|
||||||
|
import com.ortiz.touchview.TouchImageView
|
||||||
|
|
||||||
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
||||||
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420
|
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420
|
||||||
|
@ -50,9 +50,8 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||||
dialogLayout.setPadding(padding, padding, padding, padding)
|
dialogLayout.setPadding(padding, padding, padding, padding)
|
||||||
|
|
||||||
dialogLayout.orientation = LinearLayout.VERTICAL
|
dialogLayout.orientation = LinearLayout.VERTICAL
|
||||||
val imageView = PhotoView(this).apply {
|
val imageView = TouchImageView(this).apply {
|
||||||
// If it seems a lot, try opening an image of A4 format or similar
|
maxZoom = 6f
|
||||||
maximumScale = 6.0f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val displayMetrics = DisplayMetrics()
|
val displayMetrics = DisplayMetrics()
|
||||||
|
|
|
@ -24,5 +24,6 @@ data class Relationship (
|
||||||
val blocking: Boolean,
|
val blocking: Boolean,
|
||||||
val muting: Boolean,
|
val muting: Boolean,
|
||||||
val requested: Boolean,
|
val requested: Boolean,
|
||||||
@SerializedName("showing_reblogs") val showingReblogs: Boolean
|
@SerializedName("showing_reblogs") val showingReblogs: Boolean,
|
||||||
|
@SerializedName("domain_blocking") val blockingDomain: Boolean
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,9 +21,7 @@ import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.*
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
@ -31,7 +29,6 @@ import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
import com.bumptech.glide.request.RequestListener
|
import com.bumptech.glide.request.RequestListener
|
||||||
import com.bumptech.glide.request.target.Target
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.github.chrisbanes.photoview.PhotoViewAttacher
|
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
|
@ -48,11 +45,11 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
fun onPhotoTap()
|
fun onPhotoTap()
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var attacher: PhotoViewAttacher
|
|
||||||
private lateinit var photoActionsListener: PhotoActionsListener
|
private lateinit var photoActionsListener: PhotoActionsListener
|
||||||
private lateinit var toolbar: View
|
private lateinit var toolbar: View
|
||||||
private var transition = BehaviorSubject.create<Unit>()
|
private var transition = BehaviorSubject.create<Unit>()
|
||||||
private var shouldStartTransition = false
|
private var shouldStartTransition = false
|
||||||
|
|
||||||
// Volatile: Image requests happen on background thread and we want to see updates to it
|
// Volatile: Image requests happen on background thread and we want to see updates to it
|
||||||
// immediately on another thread. Atomic is an overkill for such thing.
|
// immediately on another thread. Atomic is an overkill for such thing.
|
||||||
@Volatile
|
@Volatile
|
||||||
|
@ -67,23 +64,6 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
override fun setupMediaView(url: String, previewUrl: String?) {
|
override fun setupMediaView(url: String, previewUrl: String?) {
|
||||||
descriptionView = mediaDescription
|
descriptionView = mediaDescription
|
||||||
photoView.transitionName = url
|
photoView.transitionName = url
|
||||||
attacher = PhotoViewAttacher(photoView).apply {
|
|
||||||
// Clicking outside the photo closes the viewer.
|
|
||||||
setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() }
|
|
||||||
setOnClickListener { onMediaTap() }
|
|
||||||
|
|
||||||
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
|
|
||||||
* mostly fills the screen so clicking outside is difficult. */
|
|
||||||
setOnSingleFlingListener { _, _, velocityX, velocityY ->
|
|
||||||
var result = false
|
|
||||||
if (abs(velocityY) > abs(velocityX)) {
|
|
||||||
photoActionsListener.onDismiss()
|
|
||||||
result = true
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startedTransition = false
|
startedTransition = false
|
||||||
loadImageFromNetwork(url, previewUrl, photoView)
|
loadImageFromNetwork(url, previewUrl, photoView)
|
||||||
}
|
}
|
||||||
|
@ -94,9 +74,65 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||||
|
onMediaTap()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var lastY = 0f
|
||||||
|
photoView.setOnTouchListener { _, event ->
|
||||||
|
// This part is for scaling/translating on vertical move.
|
||||||
|
// We use raw coordinates to get the correct ones during scaling
|
||||||
|
var result = true
|
||||||
|
|
||||||
|
gestureDetector.onTouchEvent(event)
|
||||||
|
|
||||||
|
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
lastY = event.rawY
|
||||||
|
} else if (!photoView.isZoomed && event.action == MotionEvent.ACTION_MOVE) {
|
||||||
|
val diff = event.rawY - lastY
|
||||||
|
// This code is to prevent transformations during page scrolling
|
||||||
|
// If we are already translating or we reached the threshold, then transform.
|
||||||
|
if (photoView.translationY != 0f || abs(diff) > 40) {
|
||||||
|
photoView.translationY += (diff)
|
||||||
|
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||||
|
photoView.scaleY = scale
|
||||||
|
photoView.scaleX = scale
|
||||||
|
lastY = event.rawY
|
||||||
|
}
|
||||||
|
return@setOnTouchListener true
|
||||||
|
} else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
|
||||||
|
onGestureEnd()
|
||||||
|
} else if (event.pointerCount >= 2 || photoView.canScrollHorizontally(1) && photoView.canScrollHorizontally(-1)) {
|
||||||
|
// Starting from here is adapted code from TouchImageView to play nice with pager.
|
||||||
|
|
||||||
|
// Can scroll horizontally checks if there's still a part of the image.
|
||||||
|
// That can be scrolled until you reach the edge multi-touch event.
|
||||||
|
val parent = view.parent
|
||||||
|
result = when (event.action) {
|
||||||
|
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
|
||||||
|
// Disallow RecyclerView to intercept touch events.
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true)
|
||||||
|
// Disable touch on view
|
||||||
|
false
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
// Allow RecyclerView to intercept touch events.
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
val arguments = this.requireArguments()
|
val arguments = this.requireArguments()
|
||||||
val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT)
|
val attachment = arguments.getParcelable<Attachment>(ARG_ATTACHMENT)
|
||||||
this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)
|
this.shouldStartTransition = arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)
|
||||||
|
@ -116,6 +152,14 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
finalizeViewSetup(url, attachment?.previewUrl, description)
|
finalizeViewSetup(url, attachment?.previewUrl, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onGestureEnd() {
|
||||||
|
if (abs(photoView.translationY) > 180) {
|
||||||
|
photoActionsListener.onDismiss()
|
||||||
|
} else {
|
||||||
|
photoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onMediaTap() {
|
private fun onMediaTap() {
|
||||||
photoActionsListener.onPhotoTap()
|
photoActionsListener.onPhotoTap()
|
||||||
}
|
}
|
||||||
|
@ -155,7 +199,6 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
.load(previewUrl)
|
.load(previewUrl)
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.onlyRetrieveFromCache(true)
|
.onlyRetrieveFromCache(true)
|
||||||
.centerInside()
|
|
||||||
.addListener(ImageRequestListener(true, isThumnailRequest = true)))
|
.addListener(ImageRequestListener(true, isThumnailRequest = true)))
|
||||||
else it
|
else it
|
||||||
}
|
}
|
||||||
|
@ -164,7 +207,6 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
.centerInside()
|
.centerInside()
|
||||||
.addListener(ImageRequestListener(false, isThumnailRequest = false))
|
.addListener(ImageRequestListener(false, isThumnailRequest = false))
|
||||||
)
|
)
|
||||||
.centerInside()
|
|
||||||
.addListener(ImageRequestListener(true, isThumnailRequest = false))
|
.addListener(ImageRequestListener(true, isThumnailRequest = false))
|
||||||
.into(photoView)
|
.into(photoView)
|
||||||
}
|
}
|
||||||
|
@ -225,13 +267,7 @@ class ViewImageFragment : ViewMediaFragment() {
|
||||||
// another branch. take() will unsubscribe after we have it to not leak menmory
|
// another branch. take() will unsubscribe after we have it to not leak menmory
|
||||||
transition
|
transition
|
||||||
.take(1)
|
.take(1)
|
||||||
.subscribe {
|
.subscribe { target.onResourceReady(resource, null) }
|
||||||
target.onResourceReady(resource, null)
|
|
||||||
// It's needed. Don't ask why, I don't know, setImageDrawable() should
|
|
||||||
// do it by itself but somehow it doesn't work automatically.
|
|
||||||
// Just do it. If you don't, image will jump around when touched.
|
|
||||||
attacher.update()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,18 +156,41 @@ class AccountViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun muteDomain(instance: String) {
|
fun blockDomain(instance: String) {
|
||||||
mastodonApi.blockDomain(instance).enqueue(object: Callback<Any> {
|
mastodonApi.blockDomain(instance).enqueue(object: Callback<Any> {
|
||||||
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
eventHub.dispatch(DomainMuteEvent(instance))
|
eventHub.dispatch(DomainMuteEvent(instance))
|
||||||
|
val relation = relationshipData.value?.data
|
||||||
|
if(relation != null) {
|
||||||
|
relationshipData.postValue(Success(relation.copy(blockingDomain = true)))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, String.format("Error muting %s", instance))
|
Log.e(TAG, "Error muting %s".format(instance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||||
Log.e(TAG, String.format("Error muting %s", instance), t)
|
Log.e(TAG, "Error muting %s".format(instance), t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unblockDomain(instance: String) {
|
||||||
|
mastodonApi.unblockDomain(instance).enqueue(object: Callback<Any> {
|
||||||
|
override fun onResponse(call: Call<Any>, response: Response<Any>) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val relation = relationshipData.value?.data
|
||||||
|
if(relation != null) {
|
||||||
|
relationshipData.postValue(Success(relation.copy(blockingDomain = false)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Error unmuting %s".format(instance))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||||
|
Log.e(TAG, "Error unmuting %s".format(instance), t)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true">
|
android:focusable="true">
|
||||||
|
|
||||||
<com.github.chrisbanes.photoview.PhotoView
|
<com.ortiz.touchview.TouchImageView
|
||||||
android:id="@+id/photoView"
|
android:id="@+id/photoView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
<string name="action_mute">Mute</string>
|
<string name="action_mute">Mute</string>
|
||||||
<string name="action_unmute">Unmute</string>
|
<string name="action_unmute">Unmute</string>
|
||||||
<string name="action_mute_domain">Mute %s</string>
|
<string name="action_mute_domain">Mute %s</string>
|
||||||
|
<string name="action_unmute_domain">Unmute %s</string>
|
||||||
<string name="action_mute_conversation">Mute conversation</string>
|
<string name="action_mute_conversation">Mute conversation</string>
|
||||||
<string name="action_unmute_conversation">Unmute conversation</string>
|
<string name="action_unmute_conversation">Unmute conversation</string>
|
||||||
<string name="action_mention">Mention</string>
|
<string name="action_mention">Mention</string>
|
||||||
|
|
Loading…
Reference in New Issue