Merge branch 'feature/translate-button'

This commit is contained in:
sk
2022-12-09 02:46:55 +01:00
9 changed files with 190 additions and 19 deletions

View File

@@ -7,4 +7,15 @@ public class GetInstance extends MastodonAPIRequest<Instance>{
public GetInstance(){ public GetInstance(){
super(HttpMethod.GET, "/instance", Instance.class); super(HttpMethod.GET, "/instance", Instance.class);
} }
public static class V2 extends MastodonAPIRequest<Instance.V2>{
public V2(){
super(HttpMethod.GET, "/instance", Instance.V2.class);
}
@Override
protected String getPathPrefix() {
return "/api/v2";
}
}
} }

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.TranslatedStatus;
public class TranslateStatus extends MastodonAPIRequest<TranslatedStatus> {
public TranslateStatus(String id) {
super(HttpMethod.POST, "/statuses/"+id+"/translate", TranslatedStatus.class);
setRequestBody(new Object());
}
}

View File

@@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Application; import org.joinmastodon.android.model.Application;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.model.PushSubscription;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
@@ -28,6 +29,7 @@ public class AccountSession{
public long filtersLastUpdated; public long filtersLastUpdated;
public List<Filter> wordFilters=new ArrayList<>(); public List<Filter> wordFilters=new ArrayList<>();
public String pushAccountID; public String pushAccountID;
public Preferences preferences;
private transient MastodonAPIController apiController; private transient MastodonAPIController apiController;
private transient StatusInteractionController statusInteractionController; private transient StatusInteractionController statusInteractionController;
private transient CacheController cacheController; private transient CacheController cacheController;

View File

@@ -22,6 +22,7 @@ import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.PushSubscriptionManager; import org.joinmastodon.android.api.PushSubscriptionManager;
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
import org.joinmastodon.android.api.requests.accounts.GetWordFilters; import org.joinmastodon.android.api.requests.accounts.GetWordFilters;
import org.joinmastodon.android.api.requests.instance.GetCustomEmojis; import org.joinmastodon.android.api.requests.instance.GetCustomEmojis;
import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount;
@@ -34,6 +35,7 @@ import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.EmojiCategory; import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Filter;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Preferences;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
import java.io.File; import java.io.File;
@@ -248,12 +250,13 @@ public class AccountSessionManager{
HashSet<String> domains=new HashSet<>(); HashSet<String> domains=new HashSet<>();
for(AccountSession session:sessions.values()){ for(AccountSession session:sessions.values()){
domains.add(session.domain.toLowerCase()); domains.add(session.domain.toLowerCase());
if(now-session.infoLastUpdated>24L*3600_000L){ // if(now-session.infoLastUpdated>24L*3600_000L){
updateSessionLocalInfo(session); updateSessionPreferences(session);
} updateSessionLocalInfo(session);
if(now-session.filtersLastUpdated>3600_000L){ // }
updateSessionWordFilters(session); // if(now-session.filtersLastUpdated>3600_000L){
} updateSessionWordFilters(session);
// }
} }
if(loadedInstances){ if(loadedInstances){
maybeUpdateCustomEmojis(domains); maybeUpdateCustomEmojis(domains);
@@ -263,10 +266,10 @@ public class AccountSessionManager{
private void maybeUpdateCustomEmojis(Set<String> domains){ private void maybeUpdateCustomEmojis(Set<String> domains){
long now=System.currentTimeMillis(); long now=System.currentTimeMillis();
for(String domain:domains){ for(String domain:domains){
Long lastUpdated=instancesLastUpdated.get(domain); // Long lastUpdated=instancesLastUpdated.get(domain);
if(lastUpdated==null || now-lastUpdated>24L*3600_000L){ // if(lastUpdated==null || now-lastUpdated>24L*3600_000L){
updateInstanceInfo(domain); updateInstanceInfo(domain);
} // }
} }
} }
@@ -288,6 +291,18 @@ public class AccountSessionManager{
.exec(session.getID()); .exec(session.getID());
} }
private void updateSessionPreferences(AccountSession session){
new GetPreferences().setCallback(new Callback<>() {
@Override
public void onSuccess(Preferences preferences) {
session.preferences=preferences;
}
@Override
public void onError(ErrorResponse error) {}
}).exec(session.getID());
}
private void updateSessionWordFilters(AccountSession session){ private void updateSessionWordFilters(AccountSession session){
new GetWordFilters() new GetWordFilters()
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@@ -313,6 +328,11 @@ public class AccountSessionManager{
public void onSuccess(Instance instance){ public void onSuccess(Instance instance){
instances.put(domain, instance); instances.put(domain, instance);
updateInstanceEmojis(instance, domain); updateInstanceEmojis(instance, domain);
try {
if (Integer.parseInt(instance.version.split("\\.")[0]) >= 4) {
updateInstanceInfoV2(domain);
}
} catch (Exception ignored) {}
} }
@Override @Override
@@ -323,6 +343,19 @@ public class AccountSessionManager{
.execNoAuth(domain); .execNoAuth(domain);
} }
public void updateInstanceInfoV2(String domain) {
new GetInstance.V2().setCallback(new Callback<>() {
@Override
public void onSuccess(Instance.V2 v2) {
Instance instanceInfo = instances.get(domain);
if (instanceInfo != null) instanceInfo.v2 = v2;
}
@Override
public void onError(ErrorResponse errorResponse) {}
}).execNoAuth(domain);
}
private void updateInstanceEmojis(Instance instance, String domain){ private void updateInstanceEmojis(Instance instance, String domain){
new GetCustomEmojis() new GetCustomEmojis()
.setCallback(new Callback<>(){ .setCallback(new Callback<>(){
@@ -398,6 +431,10 @@ public class AccountSessionManager{
return instances.get(domain); return instances.get(domain);
} }
public Instance getInstanceInfoForAccount(String account) {
return AccountSessionManager.getInstance().getInstanceInfo(instance.getAccount(account).domain);
}
public void updateAccountInfo(String id, Account account){ public void updateAccountInfo(String id, Account account){
AccountSession session=getAccount(id); AccountSession session=getAccount(id);
session.self=account; session.self=account;

View File

@@ -82,6 +82,8 @@ public class Instance extends BaseModel{
// non-standard field in some Mastodon forks // non-standard field in some Mastodon forks
public int maxTootChars; public int maxTootChars;
public V2 v2;
@Override @Override
public void postprocess() throws ObjectValidationException{ public void postprocess() throws ObjectValidationException{
super.postprocess(); super.postprocess();
@@ -176,4 +178,19 @@ public class Instance extends BaseModel{
public int minExpiration; public int minExpiration;
public int maxExpiration; public int maxExpiration;
} }
@Parcel
public static class V2 extends BaseModel {
public V2.Configuration configuration;
@Parcel
public static class Configuration {
public TranslationConfiguration translation;
}
@Parcel
public static class TranslationConfiguration{
public boolean enabled;
}
}
} }

View File

@@ -0,0 +1,7 @@
package org.joinmastodon.android.model;
public class TranslatedStatus extends BaseModel {
public String content;
public String detectedSourceLanguage;
public String provider;
}

View File

@@ -9,16 +9,25 @@ import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable; import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.model.TranslatedStatus;
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;
import org.joinmastodon.android.ui.views.LinkedTextView; import org.joinmastodon.android.ui.views.LinkedTextView;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.MovieDrawable; import me.grishka.appkit.imageloader.MovieDrawable;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
@@ -30,6 +39,12 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence parsedSpoilerText; private CharSequence parsedSpoilerText;
public boolean textSelectable; public boolean textSelectable;
public final Status status; public final Status status;
public boolean translated = false;
public TranslatedStatus translation = null;
private AccountSession session;
private Instance instanceInfo;
private boolean translateEnabled;
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){ public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status){
super(parentID, parentFragment); super(parentID, parentFragment);
@@ -41,6 +56,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
spoilerEmojiHelper=new CustomEmojiHelper(); spoilerEmojiHelper=new CustomEmojiHelper();
spoilerEmojiHelper.setText(parsedSpoilerText); spoilerEmojiHelper.setText(parsedSpoilerText);
} }
session = AccountSessionManager.getInstance().getAccount(parentFragment.getAccountID());
instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
translateEnabled = instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && instanceInfo.v2.configuration.translation.enabled;
} }
@Override @Override
@@ -65,9 +83,10 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text; private final LinkedTextView text;
private final LinearLayout spoilerHeader; private final LinearLayout spoilerHeader;
private final TextView spoilerTitle, spoilerTitleInline; private final TextView spoilerTitle, spoilerTitleInline, translateInfo;
private final View spoilerOverlay, borderTop, borderBottom; private final View spoilerOverlay, borderTop, borderBottom, textWrap, translateWrap;
private final Drawable backgroundColor, borderColor; private final Drawable backgroundColor, borderColor;
private final Button translateButton;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent); super(activity, R.layout.display_item_text, parent);
@@ -78,6 +97,10 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
spoilerOverlay=findViewById(R.id.spoiler_overlay); spoilerOverlay=findViewById(R.id.spoiler_overlay);
borderTop=findViewById(R.id.border_top); borderTop=findViewById(R.id.border_top);
borderBottom=findViewById(R.id.border_bottom); borderBottom=findViewById(R.id.border_bottom);
textWrap=findViewById(R.id.text_wrap);
translateWrap=findViewById(R.id.translate_wrap);
translateButton=findViewById(R.id.translate_btn);
translateInfo=findViewById(R.id.translate_info);
itemView.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this)); itemView.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this));
TypedValue outValue=new TypedValue(); TypedValue outValue=new TypedValue();
@@ -91,7 +114,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(TextStatusDisplayItem item){ public void onBind(TextStatusDisplayItem item){
text.setText(item.text); text.setText(item.translated
? HtmlParser.parse(item.translation.content, item.status.emojis, item.status.mentions, item.status.tags, item.parentFragment.getAccountID())
: item.text);
text.setTextIsSelectable(item.textSelectable); text.setTextIsSelectable(item.textSelectable);
spoilerTitleInline.setTextIsSelectable(item.textSelectable); spoilerTitleInline.setTextIsSelectable(item.textSelectable);
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
@@ -105,20 +130,47 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
if(item.status.spoilerRevealed){ if(item.status.spoilerRevealed){
spoilerOverlay.setVisibility(View.GONE); spoilerOverlay.setVisibility(View.GONE);
spoilerHeader.setVisibility(View.VISIBLE); spoilerHeader.setVisibility(View.VISIBLE);
text.setVisibility(View.VISIBLE); textWrap.setVisibility(View.VISIBLE);
itemView.setClickable(false); itemView.setClickable(false);
}else{ }else{
spoilerOverlay.setVisibility(View.VISIBLE); spoilerOverlay.setVisibility(View.VISIBLE);
spoilerHeader.setVisibility(View.GONE); spoilerHeader.setVisibility(View.GONE);
text.setVisibility(View.GONE); textWrap.setVisibility(View.GONE);
itemView.setClickable(true); itemView.setClickable(true);
} }
}else{ }else{
spoilerOverlay.setVisibility(View.GONE); spoilerOverlay.setVisibility(View.GONE);
spoilerHeader.setVisibility(View.GONE); spoilerHeader.setVisibility(View.GONE);
text.setVisibility(View.VISIBLE); textWrap.setVisibility(View.VISIBLE);
itemView.setClickable(false); itemView.setClickable(false);
} }
translateWrap.setVisibility(item.textSelectable && item.translateEnabled &&
!item.status.visibility.isLessVisibleThan(StatusPrivacy.UNLISTED) &&
(item.session.preferences == null || !item.status.language.equalsIgnoreCase(item.session.preferences.postingDefaultLanguage))
? View.VISIBLE : View.GONE);
translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post);
translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, item.translation.provider) : "");
translateButton.setOnClickListener(v->{
if (item.translation == null) {
new TranslateStatus(item.status.id).setCallback(new Callback<>() {
@Override
public void onSuccess(TranslatedStatus translatedStatus) {
item.translation = translatedStatus;
item.translated = true;
rebind();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(itemView.getContext());
}
}).exec(item.parentFragment.getAccountID());
} else {
item.translated = !item.translated;
rebind();
}
});
} }
@Override @Override

View File

@@ -7,6 +7,7 @@
android:paddingBottom="12dp"> android:paddingBottom="12dp">
<LinearLayout <LinearLayout
android:id="@+id/text_wrap"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@@ -47,10 +48,40 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:textSize="16sp" android:textSize="16sp"
android:textAppearance="@style/m3_body_large" android:textAppearance="@style/m3_body_large"/>
tools:text="setting up my mstdn"/>
</LinearLayout>
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
android:id="@+id/translate_wrap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical">
<Button
style="?secondaryButtonStyle"
android:background="?android:selectableItemBackground"
android:textColor="?android:textColorSecondary"
android:id="@+id/translate_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:paddingHorizontal="8dp"
android:text="Translate"/>
<TextView
android:id="@+id/translate_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="4dp"
android:layout_weight="1"
android:textColor="?android:textColorSecondary"
android:textAlignment="textEnd"
tools:text="Translated using TranslateEngine" />
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
</LinearLayout>
<LinearLayout <LinearLayout
android:visibility="gone" android:visibility="gone"

View File

@@ -48,4 +48,7 @@
<string name="sk_color_theme_brown">Brown</string> <string name="sk_color_theme_brown">Brown</string>
<string name="sk_color_theme_yellow">Yellow</string> <string name="sk_color_theme_yellow">Yellow</string>
<string name="sk_poll_allow_multiple">Allow multiple choices</string> <string name="sk_poll_allow_multiple">Allow multiple choices</string>
<string name="sk_translate_post">Translate</string>
<string name="sk_translate_show_original">Show original</string>
<string name="sk_translated_using">Translated using %s</string>
</resources> </resources>