parent
645af12c3f
commit
1b4dc01c74
|
@ -0,0 +1,13 @@
|
|||
package org.joinmastodon.android.api.requests.statuses;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.Translation;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class TranslateStatus extends MastodonAPIRequest<Translation>{
|
||||
public TranslateStatus(String id, String lang){
|
||||
super(HttpMethod.POST, "/statuses/"+id+"/translate", Translation.class);
|
||||
setRequestBody(Map.of("lang", lang));
|
||||
}
|
||||
}
|
|
@ -17,13 +17,16 @@ import org.joinmastodon.android.E;
|
|||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
|
||||
import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
|
||||
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
|
||||
import org.joinmastodon.android.events.PollUpdatedEvent;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.DisplayItemsParent;
|
||||
import org.joinmastodon.android.model.Poll;
|
||||
import org.joinmastodon.android.model.Relationship;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.model.Translation;
|
||||
import org.joinmastodon.android.ui.BetterItemAnimator;
|
||||
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
|
||||
import org.joinmastodon.android.ui.displayitems.AccountStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.GapStatusDisplayItem;
|
||||
|
@ -33,6 +36,7 @@ import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
|
|||
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.SpoilerStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
|
@ -43,6 +47,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -560,6 +565,56 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
|||
return attachmentViewsPool;
|
||||
}
|
||||
|
||||
public void togglePostTranslation(Status status, String itemID){
|
||||
switch(status.translationState){
|
||||
case LOADING -> {
|
||||
return;
|
||||
}
|
||||
case SHOWN -> {
|
||||
status.translationState=Status.TranslationState.HIDDEN;
|
||||
}
|
||||
case HIDDEN -> {
|
||||
if(status.translation!=null){
|
||||
status.translationState=Status.TranslationState.SHOWN;
|
||||
}else{
|
||||
status.translationState=Status.TranslationState.LOADING;
|
||||
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Translation result){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
status.translation=result;
|
||||
status.translationState=Status.TranslationState.SHOWN;
|
||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||
if(text!=null){
|
||||
text.updateTranslation(true);
|
||||
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
if(getActivity()==null)
|
||||
return;
|
||||
new M3AlertDialogBuilder(getActivity())
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.translation_failed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
|
||||
if(text!=null){
|
||||
text.updateTranslation(true);
|
||||
imgLoader.bindViewHolder((ImageLoaderRecyclerAdapter) list.getAdapter(), text, text.getAbsoluteAdapterPosition());
|
||||
}
|
||||
}
|
||||
|
||||
public void rebuildAllDisplayItems(){
|
||||
displayItems.clear();
|
||||
for(T item:data){
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.joinmastodon.android.model;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
|
@ -8,6 +10,8 @@ import org.parceler.Parcel;
|
|||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
@ -61,6 +65,8 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
|||
public transient boolean spoilerRevealed;
|
||||
public transient boolean hasGapAfter;
|
||||
private transient String strippedText;
|
||||
public transient TranslationState translationState=TranslationState.HIDDEN;
|
||||
public transient Translation translation;
|
||||
|
||||
public Status(){}
|
||||
|
||||
|
@ -161,6 +167,18 @@ public class Status extends BaseModel implements DisplayItemsParent{
|
|||
public Status clone(){
|
||||
Status copy=(Status) super.clone();
|
||||
copy.spoilerRevealed=false;
|
||||
copy.translationState=TranslationState.HIDDEN;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public boolean isEligibleForTranslation(){
|
||||
return !TextUtils.isEmpty(content) && !TextUtils.isEmpty(language) && !Objects.equals(Locale.getDefault().getLanguage(), language)
|
||||
&& (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED);
|
||||
}
|
||||
|
||||
public enum TranslationState{
|
||||
HIDDEN,
|
||||
SHOWN,
|
||||
LOADING
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.AllFieldsAreRequired;
|
||||
|
||||
@AllFieldsAreRequired
|
||||
public class Translation extends BaseModel{
|
||||
public String content;
|
||||
public String detectedSourceLanguage;
|
||||
public String provider;
|
||||
}
|
|
@ -41,6 +41,7 @@ import org.parceler.Parcels;
|
|||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import me.grishka.appkit.Nav;
|
||||
|
@ -195,6 +196,8 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
AccountSessionManager.getInstance().getAccount(item.accountID).getStatusInteractionController().setBookmarked(item.status, !item.status.bookmarked);
|
||||
}else if(id==R.id.share){
|
||||
UiUtils.openSystemShareSheet(activity, item.status.url);
|
||||
}else if(id==R.id.translate){
|
||||
item.parentFragment.togglePostTranslation(item.status, item.parentID);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@ -285,6 +288,15 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
|||
Account account=item.user;
|
||||
Menu menu=optionsMenu.getMenu();
|
||||
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
|
||||
boolean canTranslate=item.status!=null && item.status.getContentStatus().isEligibleForTranslation();
|
||||
MenuItem translate=menu.findItem(R.id.translate);
|
||||
translate.setVisible(canTranslate);
|
||||
if(canTranslate){
|
||||
if(item.status.translationState==Status.TranslationState.SHOWN)
|
||||
translate.setTitle(R.string.translation_show_original);
|
||||
else
|
||||
translate.setTitle(item.parentFragment.getString(R.string.translate_post, Locale.forLanguageTag(item.status.getContentStatus().language).getDisplayLanguage()));
|
||||
}
|
||||
menu.findItem(R.id.edit).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
|
||||
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);
|
||||
|
|
|
@ -3,15 +3,24 @@ package org.joinmastodon.android.ui.displayitems;
|
|||
import android.app.Activity;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewStub;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.LinkedTextView;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
|
||||
import me.grishka.appkit.imageloader.MovieDrawable;
|
||||
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
|
||||
|
@ -20,6 +29,8 @@ import me.grishka.appkit.utils.V;
|
|||
public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
private CharSequence text;
|
||||
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
|
||||
private CharSequence translatedText;
|
||||
private CustomEmojiHelper translationEmojiHelper=new CustomEmojiHelper();
|
||||
public boolean textSelectable;
|
||||
public boolean reduceTopPadding;
|
||||
public final Status status;
|
||||
|
@ -38,30 +49,54 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||
|
||||
@Override
|
||||
public int getImageCount(){
|
||||
return emojiHelper.getImageCount();
|
||||
return getCurrentEmojiHelper().getImageCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageLoaderRequest getImageRequest(int index){
|
||||
return emojiHelper.getImageRequest(index);
|
||||
return getCurrentEmojiHelper().getImageRequest(index);
|
||||
}
|
||||
|
||||
public void setTranslatedText(String text){
|
||||
Status statusForContent=status.getContentStatus();
|
||||
translatedText=HtmlParser.parse(text, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, parentFragment.getAccountID());
|
||||
translationEmojiHelper.setText(translatedText);
|
||||
}
|
||||
|
||||
private CustomEmojiHelper getCurrentEmojiHelper(){
|
||||
return status.translationState==Status.TranslationState.SHOWN ? translationEmojiHelper : emojiHelper;
|
||||
}
|
||||
|
||||
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
|
||||
private final LinkedTextView text;
|
||||
private final ViewStub translationFooterStub;
|
||||
private View translationFooter;
|
||||
private TextView translationInfo;
|
||||
private Button translationShowOriginal;
|
||||
private ProgressBar translationProgress;
|
||||
|
||||
public Holder(Activity activity, ViewGroup parent){
|
||||
super(activity, R.layout.display_item_text, parent);
|
||||
text=findViewById(R.id.text);
|
||||
translationFooterStub=findViewById(R.id.translation_info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(TextStatusDisplayItem item){
|
||||
text.setText(item.text);
|
||||
if(item.status.translationState==Status.TranslationState.SHOWN){
|
||||
if(item.translatedText==null){
|
||||
item.setTranslatedText(item.status.translation.content);
|
||||
}
|
||||
text.setText(item.translatedText);
|
||||
}else{
|
||||
text.setText(item.text);
|
||||
}
|
||||
text.setTextIsSelectable(item.textSelectable);
|
||||
text.setInvalidateOnEveryFrame(false);
|
||||
itemView.setClickable(false);
|
||||
text.setPadding(text.getPaddingLeft(), item.reduceTopPadding ? V.dp(8) : V.dp(16), text.getPaddingRight(), text.getPaddingBottom());
|
||||
text.setTextColor(UiUtils.getThemeColor(text.getContext(), item.inset ? R.attr.colorM3OnSurfaceVariant : R.attr.colorM3OnSurface));
|
||||
updateTranslation(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,5 +119,43 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||
private CustomEmojiHelper getEmojiHelper(){
|
||||
return item.emojiHelper;
|
||||
}
|
||||
|
||||
public void updateTranslation(boolean updateText){
|
||||
if(item.status==null)
|
||||
return;
|
||||
if(item.status.translationState==Status.TranslationState.HIDDEN){
|
||||
if(translationFooter!=null)
|
||||
translationFooter.setVisibility(View.GONE);
|
||||
if(updateText){
|
||||
text.setText(item.text);
|
||||
}
|
||||
}else{
|
||||
if(translationFooter==null){
|
||||
translationFooter=translationFooterStub.inflate();
|
||||
translationInfo=findViewById(R.id.translation_info_text);
|
||||
translationShowOriginal=findViewById(R.id.translation_show_original);
|
||||
translationProgress=findViewById(R.id.translation_progress);
|
||||
translationShowOriginal.setOnClickListener(v->item.parentFragment.togglePostTranslation(item.status, item.parentID));
|
||||
}else{
|
||||
translationFooter.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if(item.status.translationState==Status.TranslationState.SHOWN){
|
||||
translationProgress.setVisibility(View.GONE);
|
||||
translationInfo.setVisibility(View.VISIBLE);
|
||||
translationShowOriginal.setVisibility(View.VISIBLE);
|
||||
translationInfo.setText(translationInfo.getContext().getString(R.string.post_translated, Locale.forLanguageTag(item.status.translation.detectedSourceLanguage).getDisplayLanguage(), item.status.translation.provider));
|
||||
if(updateText){
|
||||
if(item.translatedText==null){
|
||||
item.setTranslatedText(item.status.translation.content);
|
||||
}
|
||||
text.setText(item.translatedText);
|
||||
}
|
||||
}else{ // LOADING
|
||||
translationProgress.setVisibility(View.VISIBLE);
|
||||
translationInfo.setVisibility(View.INVISIBLE);
|
||||
translationShowOriginal.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.joinmastodon.android.ui.views.LinkedTextView
|
||||
android:id="@+id/text"
|
||||
|
@ -16,4 +17,10 @@
|
|||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/m3_body_large"/>
|
||||
|
||||
</FrameLayout>
|
||||
<ViewStub
|
||||
android:id="@+id/translation_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout="@layout/footer_text_translation"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/translation_progress"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
style="?android:progressBarStyleSmall"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:clipToPadding="false"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/translation_info_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textAppearance="@style/m3_body_small"
|
||||
android:textColor="?colorM3Secondary"
|
||||
tools:text="Translated from Japanese using DeepL.com"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/translation_show_original"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
style="@style/Widget.Mastodon.M3.Button.Text"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:layout_marginEnd="-8dp"
|
||||
android:text="@string/translation_show_original"/>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<group android:id="@+id/menu_group1">
|
||||
<!-- TODO translate -->
|
||||
<item android:id="@+id/translate" android:title="@string/translate_post"/>
|
||||
<item android:id="@+id/bookmark" android:title="@string/add_bookmark"/>
|
||||
<item android:id="@+id/share" android:title="@string/button_share"/>
|
||||
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
|
||||
|
|
|
@ -589,4 +589,11 @@
|
|||
<string name="time_minutes_ago_short">%dm ago</string>
|
||||
<string name="time_hours_ago_short">%dh ago</string>
|
||||
<string name="time_days_ago_short">%dd ago</string>
|
||||
|
||||
<!-- %s is the name of the post language -->
|
||||
<string name="translate_post">Translate from %s</string>
|
||||
<!-- %1$s is the language, %2$s is the name of the translation service -->
|
||||
<string name="post_translated">Translated from %1$s using %2$s</string>
|
||||
<string name="translation_show_original">Show original</string>
|
||||
<string name="translation_failed">Translation failed. Maybe the administrator has not enabled translations on this server or this server is running an older version of Mastodon where translations are not yet supported.</string>
|
||||
</resources>
|
Loading…
Reference in New Issue