Refactor Caption Dialog to handle screen rotation (#2626) (#2693)

Co-authored-by: Prat T <pt2121@users.noreply.github.com>
This commit is contained in:
Prat 2022-09-12 09:21:00 -07:00 committed by GitHub
parent c8fc2418b8
commit 5bc680ab78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 127 additions and 77 deletions

View file

@ -68,7 +68,7 @@ import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.EmojiAdapter import com.keylesspalace.tusky.adapter.EmojiAdapter
import com.keylesspalace.tusky.adapter.LocaleAdapter import com.keylesspalace.tusky.adapter.LocaleAdapter
import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener
import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog import com.keylesspalace.tusky.components.compose.dialog.CaptionDialog
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
@ -118,7 +118,8 @@ class ComposeActivity :
OnEmojiSelectedListener, OnEmojiSelectedListener,
Injectable, Injectable,
OnReceiveContentListener, OnReceiveContentListener,
ComposeScheduleView.OnTimeSetListener { ComposeScheduleView.OnTimeSetListener,
CaptionDialog.Listener {
@Inject @Inject
lateinit var viewModelFactory: ViewModelFactory lateinit var viewModelFactory: ViewModelFactory
@ -213,9 +214,8 @@ class ComposeActivity :
val mediaAdapter = MediaPreviewAdapter( val mediaAdapter = MediaPreviewAdapter(
this, this,
onAddCaption = { item -> onAddCaption = { item ->
makeCaptionDialog(item.description, item.uri) { newDescription -> CaptionDialog.newInstance(item.localId, item.description, item.uri)
viewModel.updateDescription(item.localId, newDescription) .show(supportFragmentManager, "caption_dialog")
}
}, },
onEditImage = this::editImageInQueue, onEditImage = this::editImageInQueue,
onRemove = this::removeMediaFromQueue onRemove = this::removeMediaFromQueue
@ -1149,6 +1149,14 @@ class ComposeActivity :
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
} }
override fun onUpdateDescription(localId: Int, description: String) {
lifecycleScope.launch {
if (!viewModel.updateDescription(localId, description)) {
Toast.makeText(this@ComposeActivity, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
}
}
}
@Parcelize @Parcelize
data class ComposeOptions( data class ComposeOptions(
// Let's keep fields var until all consumers are Kotlin // Let's keep fields var until all consumers are Kotlin

View file

@ -15,19 +15,22 @@
package com.keylesspalace.tusky.components.compose.dialog package com.keylesspalace.tusky.components.compose.dialog
import android.app.Activity import android.app.Dialog
import android.content.DialogInterface import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Bundle
import android.text.InputFilter import android.text.InputFilter
import android.text.InputType import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.LifecycleOwner import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope import androidx.fragment.app.DialogFragment
import at.connyduck.sparkbutton.helpers.Utils import at.connyduck.sparkbutton.helpers.Utils
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
@ -35,85 +38,124 @@ 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.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import kotlinx.coroutines.launch
// https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32 // https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500 private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
fun <T> T.makeCaptionDialog( class CaptionDialog : DialogFragment() {
existingDescription: String?,
previewUri: Uri,
onUpdateDescription: suspend (String) -> Boolean
) where T : Activity, T : LifecycleOwner {
val dialogLayout = LinearLayout(this)
val padding = Utils.dpToPx(this, 8)
dialogLayout.setPadding(padding, padding, padding, padding)
dialogLayout.orientation = LinearLayout.VERTICAL private lateinit var listener: Listener
val imageView = PhotoView(this).apply { private lateinit var input: EditText
maximumScale = 6f
}
val margin = Utils.dpToPx(this, 4) override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
dialogLayout.addView(imageView) val context = requireContext()
(imageView.layoutParams as LinearLayout.LayoutParams).weight = 1f val dialogLayout = LinearLayout(context)
imageView.layoutParams.height = 0 val padding = Utils.dpToPx(context, 8)
(imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0) dialogLayout.setPadding(padding, padding, padding, padding)
val input = EditText(this) dialogLayout.orientation = LinearLayout.VERTICAL
input.hint = resources.getQuantityString( val imageView = PhotoView(context).apply {
R.plurals.hint_describe_for_visually_impaired, maximumScale = 6f
MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT
)
dialogLayout.addView(input)
(input.layoutParams as LinearLayout.LayoutParams).setMargins(margin, margin, margin, margin)
input.setLines(2)
input.inputType = (
InputType.TYPE_CLASS_TEXT
or InputType.TYPE_TEXT_FLAG_MULTI_LINE
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
)
input.setText(existingDescription)
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
val okListener = { dialog: DialogInterface, _: Int ->
lifecycleScope.launch {
if (!onUpdateDescription(input.text.toString())) {
showFailedCaptionMessage()
}
} }
dialog.dismiss()
val margin = Utils.dpToPx(context, 4)
dialogLayout.addView(imageView)
(imageView.layoutParams as LinearLayout.LayoutParams).weight = 1f
imageView.layoutParams.height = 0
(imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0)
input = EditText(context)
input.hint = resources.getQuantityString(
R.plurals.hint_describe_for_visually_impaired,
MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT
)
dialogLayout.addView(input)
(input.layoutParams as LinearLayout.LayoutParams).setMargins(margin, margin, margin, margin)
input.setLines(2)
input.inputType = (
InputType.TYPE_CLASS_TEXT
or InputType.TYPE_TEXT_FLAG_MULTI_LINE
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
)
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
input.setText(arguments?.getString(EXISTING_DESCRIPTION_ARG))
val localId = arguments?.getInt(LOCAL_ID_ARG) ?: error("Missing localId")
val dialog = AlertDialog.Builder(context)
.setView(dialogLayout)
.setPositiveButton(android.R.string.ok) { _, _ ->
listener.onUpdateDescription(localId, input.text.toString())
}
.setNegativeButton(android.R.string.cancel, null)
.create()
isCancelable = false
val window = dialog.window
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val previewUri =
arguments?.getParcelable<Uri>(PREVIEW_URI_ARG) ?: error("Preview Uri is null")
// Load the image and manually set it into the ImageView because it doesn't have a fixed size.
Glide.with(this)
.load(previewUri)
.downsample(DownsampleStrategy.CENTER_INSIDE)
.into(object : CustomTarget<Drawable>(4096, 4096) {
override fun onLoadCleared(placeholder: Drawable?) {
imageView.setImageDrawable(placeholder)
}
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable>?,
) {
imageView.setImageDrawable(resource)
}
})
return dialog
} }
val dialog = AlertDialog.Builder(this) override fun onSaveInstanceState(outState: Bundle) {
.setView(dialogLayout) outState.putString(DESCRIPTION_KEY, input.text.toString())
.setPositiveButton(android.R.string.ok, okListener) super.onSaveInstanceState(outState)
.setNegativeButton(android.R.string.cancel, null) }
.setCancelable(false)
.create()
val window = dialog.window override fun onCreateView(
window?.setSoftInputMode( inflater: LayoutInflater,
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE container: ViewGroup?,
) savedInstanceState: Bundle?,
): View? {
savedInstanceState?.getString(DESCRIPTION_KEY)?.let {
input.setText(it)
}
return super.onCreateView(inflater, container, savedInstanceState)
}
dialog.show() override fun onAttach(context: Context) {
super.onAttach(context)
listener = context as? Listener ?: error("Activity is not ComposeCaptionDialog.Listener")
}
// Load the image and manually set it into the ImageView because it doesn't have a fixed size. interface Listener {
Glide.with(this) fun onUpdateDescription(localId: Int, description: String)
.load(previewUri) }
.downsample(DownsampleStrategy.CENTER_INSIDE)
.into(object : CustomTarget<Drawable>(4096, 4096) {
override fun onLoadCleared(placeholder: Drawable?) {
imageView.setImageDrawable(placeholder)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) { companion object {
imageView.setImageDrawable(resource) fun newInstance(
} localId: Int,
}) existingDescription: String?,
} previewUri: Uri,
) = CaptionDialog().apply {
private fun Activity.showFailedCaptionMessage() { arguments = bundleOf(
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show() LOCAL_ID_ARG to localId,
EXISTING_DESCRIPTION_ARG to existingDescription,
PREVIEW_URI_ARG to previewUri,
)
}
private const val DESCRIPTION_KEY = "description"
private const val EXISTING_DESCRIPTION_ARG = "existing_description"
private const val PREVIEW_URI_ARG = "preview_uri"
private const val LOCAL_ID_ARG = "local_id"
}
} }