Improve translating posts via Mastodon api (#4463)

This does 4 things:

- Alt text is now translated when opening media of translated posts.
Previously only the long-press alt text was translated.
- The translate button is now hidden on non-public posts. The Mastodon
api returns 403 there.
- Translated posts will only be collapsible when the original was
collapsible as well. It is just weird when an "show more" button
suddenly appears because the post got longer by translating it.
- The translation status and the untranslate button are now shown below
each other instead of next to each other. Looks way better on smaller
display or long texts.

Before / After
<img
src="https://github.com/tuskyapp/Tusky/assets/10157047/2cadd15b-2e28-4989-9bd3-d3bdd4c75329"
width="320"/> <img
src="https://github.com/tuskyapp/Tusky/assets/10157047/0ecab094-6c96-49a5-bc99-aa56b7fe2ec2"
width="320"/>
This commit is contained in:
Konrad Pozniak 2024-05-26 08:06:51 +02:00 committed by GitHub
parent 9805bc2cce
commit 32dea86502
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 97 additions and 109 deletions

View File

@ -59,7 +59,15 @@ class AccountMediaRemoteMediator(
}
val attachments = statuses.flatMap { status ->
AttachmentViewData.list(status, activeAccount.alwaysShowSensitiveMedia)
status.attachments.map { attachment ->
AttachmentViewData(
attachment = attachment,
statusId = status.id,
statusUrl = status.url.orEmpty(),
sensitive = status.sensitive,
isRevealed = activeAccount.alwaysShowSensitiveMedia || !status.sensitive
)
}
}
if (loadType == LoadType.REFRESH) {

View File

@ -319,7 +319,7 @@ class ConversationsFragment :
adapter.peek(position)?.let { conversation ->
viewMedia(
attachmentIndex,
AttachmentViewData.list(conversation.lastStatus.status),
AttachmentViewData.list(conversation.lastStatus),
view
)
}

View File

@ -397,7 +397,7 @@ class NotificationsFragment :
}
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
val status = adapter.peek(position)?.asStatusOrNull()?.status ?: return
val status = adapter.peek(position)?.asStatusOrNull() ?: return
super.viewMedia(attachmentIndex, AttachmentViewData.list(status), view)
}

View File

@ -18,9 +18,10 @@ package com.keylesspalace.tusky.components.report.adapter
import android.view.View
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.interfaces.LinkListener
import com.keylesspalace.tusky.viewdata.StatusViewData
interface AdapterHandler : LinkListener {
fun showMedia(v: View?, status: Status?, idx: Int)
fun showMedia(v: View?, status: StatusViewData.Concrete, idx: Int)
fun setStatusChecked(status: Status, isChecked: Boolean)
fun isStatusChecked(id: String): Boolean
}

View File

@ -59,7 +59,7 @@ class StatusViewHolder(
private val previewListener = object : StatusViewHelper.MediaPreviewListener {
override fun onViewMedia(v: View?, idx: Int) {
viewdata()?.let { viewdata ->
adapterHandler.showMedia(v, viewdata.status, idx)
adapterHandler.showMedia(v, viewdata, idx)
}
}

View File

@ -53,6 +53,7 @@ import com.keylesspalace.tusky.util.StatusDisplayOptions
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import com.keylesspalace.tusky.viewdata.AttachmentViewData
import com.keylesspalace.tusky.viewdata.StatusViewData
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
@ -83,14 +84,13 @@ class ReportStatusesFragment :
private var snackbarErrorRetry: Snackbar? = null
override fun showMedia(v: View?, status: Status?, idx: Int) {
status?.actionableStatus?.let { actionable ->
when (actionable.attachments[idx].type) {
override fun showMedia(v: View?, status: StatusViewData.Concrete, idx: Int) {
when (status.attachments[idx].type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
val attachments = AttachmentViewData.list(actionable)
val attachments = AttachmentViewData.list(status)
val intent = ViewMediaActivity.newIntent(context, attachments, idx)
if (v != null) {
val url = actionable.attachments[idx].url
val url = status.attachments[idx].url
ViewCompat.setTransitionName(v, url)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
requireActivity(),
@ -106,7 +106,6 @@ class ReportStatusesFragment :
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)

View File

@ -182,17 +182,17 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
}
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
searchAdapter.peek(position)?.status?.actionableStatus?.let { actionable ->
when (actionable.attachments[attachmentIndex].type) {
searchAdapter.peek(position)?.let { status ->
when (status.attachments[attachmentIndex].type) {
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
val attachments = AttachmentViewData.list(actionable)
val attachments = AttachmentViewData.list(status)
val intent = ViewMediaActivity.newIntent(
context,
attachments,
attachmentIndex
)
if (view != null) {
val url = actionable.attachments[attachmentIndex].url
val url = status.attachments[attachmentIndex].url
ViewCompat.setTransitionName(view, url)
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
requireActivity(),
@ -206,7 +206,7 @@ class SearchStatusesFragment : SearchFragment<StatusViewData.Concrete>(), Status
}
Attachment.Type.UNKNOWN -> {
context?.openLink(actionable.attachments[attachmentIndex].url)
context?.openLink(status.attachments[attachmentIndex].url)
}
}
}

View File

@ -534,7 +534,7 @@ class TimelineFragment :
val status = adapter.peek(position)?.asStatusOrNull() ?: return
super.viewMedia(
attachmentIndex,
AttachmentViewData.list(status.actionable),
AttachmentViewData.list(status),
view
)
}

View File

@ -381,7 +381,7 @@ class ViewThreadFragment :
}
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
val status = adapter.currentList[position].status
val status = adapter.currentList[position]
super.viewMedia(
attachmentIndex,
list(status, alwaysShowSensitiveMedia),

View File

@ -249,11 +249,12 @@ abstract class SFragment : Fragment() {
)
}
// translation not there for your own posts
// translation not there for your own posts, posts already in your language or non-public posts
menu.findItem(R.id.status_translate)?.let { translateItem ->
translateItem.isVisible = onMoreTranslate != null &&
!status.language.equals(Locale.getDefault().language, ignoreCase = true) &&
instanceInfoRepository.cachedInstanceInfoOrFallback.translationEnabled == true
instanceInfoRepository.cachedInstanceInfoOrFallback.translationEnabled == true &&
(status.visibility == Status.Visibility.PUBLIC || status.visibility == Status.Visibility.UNLISTED)
translateItem.setTitle(if (translation != null) R.string.action_show_original else R.string.action_translate)
}

View File

@ -17,7 +17,6 @@ package com.keylesspalace.tusky.viewdata
import android.os.Parcelable
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Status
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -36,17 +35,16 @@ data class AttachmentViewData(
companion object {
@JvmStatic
fun list(
status: Status,
status: StatusViewData.Concrete,
alwaysShowSensitiveMedia: Boolean = false
): List<AttachmentViewData> {
val actionable = status.actionableStatus
return actionable.attachments.map { attachment ->
return status.attachments.map { attachment ->
AttachmentViewData(
attachment = attachment,
statusId = actionable.id,
statusUrl = actionable.url!!,
sensitive = actionable.sensitive,
isRevealed = alwaysShowSensitiveMedia || !actionable.sensitive
statusId = status.actionableId,
statusUrl = status.actionable.url!!,
sensitive = status.actionable.sensitive,
isRevealed = alwaysShowSensitiveMedia || !status.actionable.sensitive
)
}
}

View File

@ -82,10 +82,12 @@ sealed class StatusViewData {
/**
* Specifies whether the content of this post is long enough to be automatically
* collapsed or if it should show all content regardless.
* Translated posts only show the button if the original post had it as well.
*
* @return Whether the post is collapsible or never collapsed.
*/
val isCollapsible: Boolean = shouldTrimStatus(this.content)
val isCollapsible: Boolean = shouldTrimStatus(this.content) &&
(translation == null || shouldTrimStatus(actionable.content.parseAsMastodonHtml()))
val actionable: Status
get() = status.actionableStatus

View File

@ -103,46 +103,35 @@
app:layout_constraintTop_toTopOf="@id/status_display_name"
tools:text="13:37" />
<Button
android:id="@+id/status_button_untranslate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_show_original"
style="@style/TuskyButton.TextButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/status_translation_status"
app:layout_constraintBottom_toBottomOf="@id/status_translation_status"
android:layout_marginEnd="10dp"
android:visibility="gone"
tools:visibility="visible"
android:minHeight="0dp" />
<TextView
android:id="@+id/status_translation_status"
style="@style/TextSizeSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/TextSizeSmall"
tools:text="Translated from Lang by Service"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintEnd_toStartOf="@id/status_button_untranslate"
app:layout_constraintTop_toBottomOf="@id/status_username"
android:layout_marginEnd="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="10dp"
android:lineSpacingMultiplier="1.1"
android:maxLines="4"
android:visibility="gone"
tools:visibility="visible"
android:minLines="2"
android:lineSpacingMultiplier="1.1"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
app:layout_constraintBottom_toTopOf="@id/status_translation_barrier"
/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_username"
tools:text="Translated from Lang by Service"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/status_translation_barrier"
android:layout_width="match_parent"
<Button
android:id="@+id/status_button_untranslate"
style="@style/TuskyButton.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="status_translation_status, status_button_untranslate" />
android:paddingHorizontal="0dp"
android:layout_marginEnd="10dp"
android:minHeight="0dp"
android:text="@string/action_show_original"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_translation_status"
tools:visibility="visible" />
<com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/status_content_warning_description"
@ -157,7 +146,7 @@
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_translation_barrier"
app:layout_constraintTop_toBottomOf="@id/status_button_untranslate"
tools:text="content warning which is very long and it doesn't fit"
tools:visibility="visible" />
@ -359,13 +348,13 @@
android:layout_marginStart="-14dp"
android:contentDescription="@string/action_reply"
android:importantForAccessibility="no"
tools:ignore="NegativeMargin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/status_inset"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintStart_toStartOf="@id/status_display_name"
app:layout_constraintTop_toBottomOf="@id/status_poll_description"
app:srcCompat="@drawable/ic_reply_24dp" />
app:srcCompat="@drawable/ic_reply_24dp"
tools:ignore="NegativeMargin" />
<TextView
android:id="@+id/status_replies"

View File

@ -78,45 +78,35 @@
app:layout_constraintTop_toBottomOf="@id/status_display_name"
tools:text="\@ConnyDuck\@mastodon.social" />
<Button
android:id="@+id/status_button_untranslate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/action_show_original"
app:layout_constraintTop_toTopOf="@id/status_translation_status"
app:layout_constraintBottom_toBottomOf="@id/status_translation_status"
style="@style/TuskyButton.TextButton"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="14dp"
android:minHeight="0dp"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/status_translation_status"
style="@style/TextSizeSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/TextSizeSmall"
android:layout_marginStart="14dp"
tools:text="Translated from blah using service"
android:layout_marginTop="8dp"
android:layout_marginEnd="14dp"
android:lineSpacingMultiplier="1.1"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_avatar"
app:layout_constraintEnd_toStartOf="@id/status_button_untranslate"
android:minLines="2"
android:lineSpacingMultiplier="1.1"
android:gravity="center_vertical"
android:layout_marginTop="4dp"
android:visibility="gone"
tools:visibility="visible"
android:layout_marginEnd="4dp"/>
tools:text="Translated from blah using service"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/status_translation_barrier"
android:layout_width="match_parent"
<Button
android:id="@+id/status_button_untranslate"
style="@style/TuskyButton.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="status_translation_status, status_button_untranslate" />
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:minHeight="0dp"
android:paddingHorizontal="0dp"
android:text="@string/action_show_original"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_translation_status"
tools:visibility="visible" />
<com.keylesspalace.tusky.view.ClickableSpanTextView
android:id="@+id/status_content_warning_description"
@ -133,7 +123,7 @@
android:textSize="?attr/status_text_large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/status_translation_barrier"
app:layout_constraintTop_toBottomOf="@id/status_button_untranslate"
tools:text="CW this is a long long long long long long long long content warning" />
<com.google.android.material.button.MaterialButton