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.LocaleAdapter
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.view.ComposeOptionsListener
import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView
@ -118,7 +118,8 @@ class ComposeActivity :
OnEmojiSelectedListener,
Injectable,
OnReceiveContentListener,
ComposeScheduleView.OnTimeSetListener {
ComposeScheduleView.OnTimeSetListener,
CaptionDialog.Listener {
@Inject
lateinit var viewModelFactory: ViewModelFactory
@ -213,9 +214,8 @@ class ComposeActivity :
val mediaAdapter = MediaPreviewAdapter(
this,
onAddCaption = { item ->
makeCaptionDialog(item.description, item.uri) { newDescription ->
viewModel.updateDescription(item.localId, newDescription)
}
CaptionDialog.newInstance(item.localId, item.description, item.uri)
.show(supportFragmentManager, "caption_dialog")
},
onEditImage = this::editImageInQueue,
onRemove = this::removeMediaFromQueue
@ -1149,6 +1149,14 @@ class ComposeActivity :
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
data class ComposeOptions(
// Let's keep fields var until all consumers are Kotlin

View File

@ -15,19 +15,22 @@
package com.keylesspalace.tusky.components.compose.dialog
import android.app.Activity
import android.content.DialogInterface
import android.app.Dialog
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.text.InputFilter
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import at.connyduck.sparkbutton.helpers.Utils
import com.bumptech.glide.Glide
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.github.chrisbanes.photoview.PhotoView
import com.keylesspalace.tusky.R
import kotlinx.coroutines.launch
// https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
fun <T> T.makeCaptionDialog(
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)
class CaptionDialog : DialogFragment() {
dialogLayout.orientation = LinearLayout.VERTICAL
val imageView = PhotoView(this).apply {
maximumScale = 6f
}
private lateinit var listener: Listener
private lateinit var input: EditText
val margin = Utils.dpToPx(this, 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)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val context = requireContext()
val dialogLayout = LinearLayout(context)
val padding = Utils.dpToPx(context, 8)
dialogLayout.setPadding(padding, padding, padding, padding)
val input = EditText(this)
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.setText(existingDescription)
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
val okListener = { dialog: DialogInterface, _: Int ->
lifecycleScope.launch {
if (!onUpdateDescription(input.text.toString())) {
showFailedCaptionMessage()
}
dialogLayout.orientation = LinearLayout.VERTICAL
val imageView = PhotoView(context).apply {
maximumScale = 6f
}
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)
.setView(dialogLayout)
.setPositiveButton(android.R.string.ok, okListener)
.setNegativeButton(android.R.string.cancel, null)
.setCancelable(false)
.create()
override fun onSaveInstanceState(outState: Bundle) {
outState.putString(DESCRIPTION_KEY, input.text.toString())
super.onSaveInstanceState(outState)
}
val window = dialog.window
window?.setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
)
override fun onCreateView(
inflater: LayoutInflater,
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.
Glide.with(this)
.load(previewUri)
.downsample(DownsampleStrategy.CENTER_INSIDE)
.into(object : CustomTarget<Drawable>(4096, 4096) {
override fun onLoadCleared(placeholder: Drawable?) {
imageView.setImageDrawable(placeholder)
}
interface Listener {
fun onUpdateDescription(localId: Int, description: String)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
imageView.setImageDrawable(resource)
}
})
}
private fun Activity.showFailedCaptionMessage() {
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
companion object {
fun newInstance(
localId: Int,
existingDescription: String?,
previewUri: Uri,
) = CaptionDialog().apply {
arguments = bundleOf(
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"
}
}