feat: translate media attachments and poll options (#916)

* feat(status): translate media attachments

* feat(status): translate poll options

* fix(status/translation): do not require all fields

* feat(status/translation): support translating spoiler
This commit is contained in:
FineFindus
2023-11-10 20:44:06 +01:00
committed by GitHub
parent 06698d3c52
commit c85af5502d
6 changed files with 98 additions and 22 deletions

View File

@@ -494,7 +494,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1)); spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
} }
pollItems.clear(); pollItems.clear();
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems); StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
if(spoilerItem!=null){ if(spoilerItem!=null){
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear(); spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems); spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
@@ -859,13 +859,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
return; return;
status.translation=result; status.translation=result;
status.translationState=Status.TranslationState.SHOWN; status.translationState=Status.TranslationState.SHOWN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); updateTranslation(itemID);
if(text!=null){
text.updateTranslation(true);
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
} }
@Override @Override
@@ -873,12 +867,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
if(getActivity()==null) if(getActivity()==null)
return; return;
status.translationState=Status.TranslationState.HIDDEN; status.translationState=Status.TranslationState.HIDDEN;
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); updateTranslation(itemID);
if(text!=null){
text.updateTranslation(true);
}else{
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.translation_failed) .setMessage(R.string.translation_failed)
@@ -890,6 +879,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
} }
} }
updateTranslation(itemID);
}
private void updateTranslation(String itemID) {
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){ if(text!=null){
text.updateTranslation(true); text.updateTranslation(true);
@@ -897,6 +890,22 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}else{ }else{
notifyItemChanged(itemID, TextStatusDisplayItem.class); notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null){
spoiler.rebind();
}
MediaGridStatusDisplayItem.Holder media=findHolderOfType(itemID, MediaGridStatusDisplayItem.Holder.class);
if (media!=null) {
media.rebind();
}
for(int i=0;i<list.getChildCount();i++){
if(list.getChildViewHolder(list.getChildAt(i)) instanceof PollOptionStatusDisplayItem.Holder item){
item.rebind();
}
}
} }
public void rebuildAllDisplayItems(){ public void rebuildAllDisplayItems(){

View File

@@ -1,10 +1,30 @@
package org.joinmastodon.android.model; package org.joinmastodon.android.model;
import org.joinmastodon.android.api.AllFieldsAreRequired;
@AllFieldsAreRequired import org.joinmastodon.android.api.RequiredField;
public class Translation extends BaseModel{ public class Translation extends BaseModel{
@RequiredField
public String content; public String content;
@RequiredField
public String detectedSourceLanguage; public String detectedSourceLanguage;
@RequiredField
public String provider; public String provider;
public String spoilerText;
public MediaAttachment[] mediaAttachments;
public PollTranslation poll;
public static class MediaAttachment {
public String id;
public String description;
}
public static class PollTranslation {
public String id;
public PollOption[] options;
}
public static class PollOption {
public String title;
}
} }

View File

@@ -12,6 +12,7 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -26,6 +27,7 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.Translation;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.PhotoLayoutHelper;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable; import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
@@ -38,7 +40,12 @@ import org.joinmastodon.android.ui.views.MediaGridLayout;
import org.joinmastodon.android.utils.TypedObjectPool; import org.joinmastodon.android.utils.TypedObjectPool;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -52,6 +59,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private PhotoLayoutHelper.TiledLayoutResult tiledLayout; private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
private final TypedObjectPool<GridItemType, MediaAttachmentViewController> viewPool; private final TypedObjectPool<GridItemType, MediaAttachmentViewController> viewPool;
private final List<Attachment> attachments; private final List<Attachment> attachments;
private final Map<String, Pair<String, String>> translatedAttachments = new HashMap<>();
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>(); private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
public final Status status; public final Status status;
public String sensitiveTitle; public String sensitiveTitle;
@@ -189,6 +197,25 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
c.btnsWrap.setAlpha(1f); c.btnsWrap.setAlpha(1f);
} }
controllers.add(c); controllers.add(c);
if (item.status.translation != null){
if(item.status.translationState==Status.TranslationState.SHOWN){
if(!item.translatedAttachments.containsKey(att.id)){
Optional<Translation.MediaAttachment> translatedAttachment=Arrays.stream(item.status.translation.mediaAttachments).filter(mediaAttachment->mediaAttachment.id.equals(att.id)).findFirst();
translatedAttachment.ifPresent(mediaAttachment->{
item.translatedAttachments.put(mediaAttachment.id, new Pair<>(att.description, mediaAttachment.description));
att.description=mediaAttachment.description;
});
}else{
//SAFETY: must be non-null, as we check if the map contains the attachment before
att.description=Objects.requireNonNull(item.translatedAttachments.get(att.id)).second;
}
}else{
if (item.translatedAttachments.containsKey(att.id)) {
att.description=Objects.requireNonNull(item.translatedAttachments.get(att.id)).first;
}
}
}
c.bind(att, item.status); c.bind(att, item.status);
i++; i++;
} }

View File

@@ -11,6 +11,7 @@ import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper; import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@@ -23,6 +24,7 @@ import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class PollOptionStatusDisplayItem extends StatusDisplayItem{ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private CharSequence text; private CharSequence text;
private CharSequence translatedText;
public final Poll.Option option; public final Poll.Option option;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private boolean showResults; private boolean showResults;
@@ -30,12 +32,15 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
private boolean isMostVoted; private boolean isMostVoted;
private final int optionIndex; private final int optionIndex;
public final Poll poll; public final Poll poll;
public final Status status;
public PollOptionStatusDisplayItem(String parentID, Poll poll, int optionIndex, BaseStatusListFragment parentFragment){
public PollOptionStatusDisplayItem(String parentID, Poll poll, int optionIndex, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment); super(parentID, parentFragment);
this.optionIndex=optionIndex; this.optionIndex=optionIndex;
option=poll.options.get(optionIndex); option=poll.options.get(optionIndex);
this.poll=poll; this.poll=poll;
this.status=status;
text=HtmlParser.parseCustomEmoji(option.title, poll.emojis); text=HtmlParser.parseCustomEmoji(option.title, poll.emojis);
emojiHelper.setText(text); emojiHelper.setText(text);
showResults=poll.isExpired() || poll.voted; showResults=poll.isExpired() || poll.voted;
@@ -84,7 +89,14 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(PollOptionStatusDisplayItem item){ public void onBind(PollOptionStatusDisplayItem item){
if (item.status.translation != null && item.status.translationState == Status.TranslationState.SHOWN) {
if(item.translatedText==null){
item.translatedText=item.status.translation.poll.options[item.optionIndex].title;
}
text.setText(item.translatedText);
} else {
text.setText(item.text); text.setText(item.text);
}
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE); percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults); itemView.setClickable(!item.showResults);
icon.setImageDrawable(itemView.getContext().getDrawable(item.poll.multiple ? icon.setImageDrawable(itemView.getContext().getDrawable(item.poll.multiple ?

View File

@@ -27,6 +27,7 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
public final Status status; public final Status status;
public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>(); public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>();
private final CharSequence parsedTitle; private final CharSequence parsedTitle;
private CharSequence translatedTitle;
private final CustomEmojiHelper emojiHelper; private final CustomEmojiHelper emojiHelper;
private final Type type; private final Type type;
private final int attachmentCount; private final int attachmentCount;
@@ -90,7 +91,14 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(SpoilerStatusDisplayItem item){ public void onBind(SpoilerStatusDisplayItem item){
if(item.status.translationState==Status.TranslationState.SHOWN){
if(item.translatedTitle==null){
item.translatedTitle=item.status.translation.spoilerText;
}
title.setText(item.translatedTitle);
}else{
title.setText(item.parsedTitle); title.setText(item.parsedTitle);
}
action.setText(item.status.spoilerRevealed ? R.string.spoiler_hide : R.string.sk_spoiler_show); action.setText(item.status.spoilerRevealed ? R.string.spoiler_hide : R.string.sk_spoiler_show);
itemView.setPadding( itemView.setPadding(
itemView.getPaddingLeft(), itemView.getPaddingLeft(),

View File

@@ -285,7 +285,7 @@ public abstract class StatusDisplayItem{
} }
} }
if(statusForContent.poll!=null){ if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, contentItems); buildPollItems(parentID, fragment, statusForContent.poll, status, contentItems);
} }
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){ if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent)); contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent));
@@ -339,10 +339,10 @@ public abstract class StatusDisplayItem{
); );
} }
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items){ public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items){
int i=0; int i=0;
for(Poll.Option opt:poll.options){ for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment)); items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));
i++; i++;
} }
items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll)); items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll));