Compose: language selection
This commit is contained in:
parent
89501271ce
commit
15883f2138
|
@ -1,19 +1,29 @@
|
||||||
package org.joinmastodon.android.api.session;
|
package org.joinmastodon.android.api.session;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.CacheController;
|
import org.joinmastodon.android.api.CacheController;
|
||||||
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.StatusInteractionController;
|
import org.joinmastodon.android.api.StatusInteractionController;
|
||||||
|
import org.joinmastodon.android.api.requests.accounts.GetPreferences;
|
||||||
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;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import me.grishka.appkit.api.Callback;
|
||||||
|
import me.grishka.appkit.api.ErrorResponse;
|
||||||
|
|
||||||
public class AccountSession{
|
public class AccountSession{
|
||||||
|
private static final String TAG="AccountSession";
|
||||||
|
|
||||||
public Token token;
|
public Token token;
|
||||||
public Account self;
|
public Account self;
|
||||||
public String domain;
|
public String domain;
|
||||||
|
@ -29,6 +39,7 @@ public class AccountSession{
|
||||||
public List<Filter> wordFilters=new ArrayList<>();
|
public List<Filter> wordFilters=new ArrayList<>();
|
||||||
public String pushAccountID;
|
public String pushAccountID;
|
||||||
public AccountActivationInfo activationInfo;
|
public AccountActivationInfo activationInfo;
|
||||||
|
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;
|
||||||
|
@ -77,4 +88,22 @@ public class AccountSession{
|
||||||
public String getFullUsername(){
|
public String getFullUsername(){
|
||||||
return '@'+self.username+'@'+domain;
|
return '@'+self.username+'@'+domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void reloadPreferences(Consumer<Preferences> callback){
|
||||||
|
new GetPreferences()
|
||||||
|
.setCallback(new Callback<>(){
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Preferences result){
|
||||||
|
preferences=result;
|
||||||
|
callback.accept(result);
|
||||||
|
AccountSessionManager.getInstance().writeAccountsFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(ErrorResponse error){
|
||||||
|
Log.w(TAG, "Failed to load preferences for account "+getID()+": "+error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec(getID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ import org.joinmastodon.android.ui.text.HtmlParser;
|
||||||
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
|
||||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ComposeAutocompleteViewController;
|
import org.joinmastodon.android.ui.viewcontrollers.ComposeAutocompleteViewController;
|
||||||
|
import org.joinmastodon.android.ui.viewcontrollers.ComposeLanguageAlertViewController;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ComposeMediaViewController;
|
import org.joinmastodon.android.ui.viewcontrollers.ComposeMediaViewController;
|
||||||
import org.joinmastodon.android.ui.viewcontrollers.ComposePollViewController;
|
import org.joinmastodon.android.ui.viewcontrollers.ComposePollViewController;
|
||||||
import org.joinmastodon.android.ui.views.ComposeEditText;
|
import org.joinmastodon.android.ui.views.ComposeEditText;
|
||||||
|
@ -122,7 +123,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
private String accountID;
|
private String accountID;
|
||||||
private int charCount, charLimit, trimmedCharCount;
|
private int charCount, charLimit, trimmedCharCount;
|
||||||
|
|
||||||
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn;
|
private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, languageBtn;
|
||||||
private TextView replyText;
|
private TextView replyText;
|
||||||
private Button visibilityBtn;
|
private Button visibilityBtn;
|
||||||
private LinearLayout bottomBar;
|
private LinearLayout bottomBar;
|
||||||
|
@ -142,6 +143,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
|
private StatusPrivacy statusVisibility=StatusPrivacy.PUBLIC;
|
||||||
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
private ComposeAutocompleteSpan currentAutocompleteSpan;
|
||||||
private FrameLayout mainEditTextWrap;
|
private FrameLayout mainEditTextWrap;
|
||||||
|
private ComposeLanguageAlertViewController.SelectedOption postLang;
|
||||||
|
|
||||||
private ComposeAutocompleteViewController autocompleteViewController;
|
private ComposeAutocompleteViewController autocompleteViewController;
|
||||||
private ComposePollViewController pollViewController=new ComposePollViewController(this);
|
private ComposePollViewController pollViewController=new ComposePollViewController(this);
|
||||||
|
@ -190,9 +192,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
else
|
else
|
||||||
charLimit=500;
|
charLimit=500;
|
||||||
|
|
||||||
if(editingStatus==null)
|
|
||||||
loadDefaultStatusVisibility(savedInstanceState);
|
|
||||||
setTitle(editingStatus==null ? R.string.new_post : R.string.edit_post);
|
setTitle(editingStatus==null ? R.string.new_post : R.string.edit_post);
|
||||||
|
if(savedInstanceState!=null)
|
||||||
|
postLang=Parcels.unwrap(savedInstanceState.getParcelable("postLang"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -251,12 +253,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
emojiBtn=view.findViewById(R.id.btn_emoji);
|
emojiBtn=view.findViewById(R.id.btn_emoji);
|
||||||
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
spoilerBtn=view.findViewById(R.id.btn_spoiler);
|
||||||
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
visibilityBtn=view.findViewById(R.id.btn_visibility);
|
||||||
|
languageBtn=view.findViewById(R.id.btn_language);
|
||||||
replyText=view.findViewById(R.id.reply_text);
|
replyText=view.findViewById(R.id.reply_text);
|
||||||
|
|
||||||
mediaBtn.setOnClickListener(v->openFilePicker());
|
mediaBtn.setOnClickListener(v->openFilePicker());
|
||||||
pollBtn.setOnClickListener(v->togglePoll());
|
pollBtn.setOnClickListener(v->togglePoll());
|
||||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||||
|
languageBtn.setOnClickListener(v->showLanguageAlert());
|
||||||
visibilityBtn.setOnClickListener(this::onVisibilityClick);
|
visibilityBtn.setOnClickListener(this::onVisibilityClick);
|
||||||
Drawable arrow=getResources().getDrawable(R.drawable.ic_baseline_arrow_drop_down_18, getActivity().getTheme()).mutate();
|
Drawable arrow=getResources().getDrawable(R.drawable.ic_baseline_arrow_drop_down_18, getActivity().getTheme()).mutate();
|
||||||
arrow.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
|
arrow.setTint(UiUtils.getThemeColor(getActivity(), R.attr.colorM3OnSurface));
|
||||||
|
@ -343,6 +347,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
mediaViewController.onSaveInstanceState(outState);
|
mediaViewController.onSaveInstanceState(outState);
|
||||||
outState.putBoolean("hasSpoiler", hasSpoiler);
|
outState.putBoolean("hasSpoiler", hasSpoiler);
|
||||||
outState.putSerializable("visibility", statusVisibility);
|
outState.putSerializable("visibility", statusVisibility);
|
||||||
|
outState.putParcelable("postLang", Parcels.wrap(postLang));
|
||||||
if(currentAutocompleteSpan!=null){
|
if(currentAutocompleteSpan!=null){
|
||||||
Editable e=mainEditText.getText();
|
Editable e=mainEditText.getText();
|
||||||
outState.putInt("autocompleteStart", e.getSpanStart(currentAutocompleteSpan));
|
outState.putInt("autocompleteStart", e.getSpanStart(currentAutocompleteSpan));
|
||||||
|
@ -358,6 +363,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState){
|
public void onViewCreated(View view, Bundle savedInstanceState){
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
if(editingStatus==null)
|
||||||
|
loadDefaultStatusVisibility(savedInstanceState);
|
||||||
contentView.setSizeListener(emojiKeyboard::onContentViewSizeChanged);
|
contentView.setSizeListener(emojiKeyboard::onContentViewSizeChanged);
|
||||||
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
|
||||||
mainEditText.requestFocus();
|
mainEditText.requestFocus();
|
||||||
|
@ -650,6 +657,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
if(hasSpoiler && spoilerEdit.length()>0){
|
if(hasSpoiler && spoilerEdit.length()>0){
|
||||||
req.spoilerText=spoilerEdit.getText().toString();
|
req.spoilerText=spoilerEdit.getText().toString();
|
||||||
}
|
}
|
||||||
|
if(postLang!=null){
|
||||||
|
req.language=postLang.locale.toLanguageTag();
|
||||||
|
}
|
||||||
if(uuid==null)
|
if(uuid==null)
|
||||||
uuid=UUID.randomUUID().toString();
|
uuid=UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
@ -867,45 +877,43 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
menu.show();
|
menu.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDefaultStatusVisibility(Bundle savedInstanceState) {
|
private void loadDefaultStatusVisibility(Bundle savedInstanceState){
|
||||||
if(getArguments().containsKey("replyTo")){
|
if(getArguments().containsKey("replyTo")){
|
||||||
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
|
||||||
statusVisibility = replyTo.visibility;
|
statusVisibility=replyTo.visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A saved privacy setting from a previous compose session wins over the reply visibility
|
// A saved privacy setting from a previous compose session wins over the reply visibility
|
||||||
if(savedInstanceState !=null){
|
if(savedInstanceState!=null){
|
||||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
statusVisibility=(StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||||
}
|
}
|
||||||
|
|
||||||
new GetPreferences()
|
Preferences prevPrefs=AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
||||||
.setCallback(new Callback<>(){
|
if(prevPrefs!=null){
|
||||||
@Override
|
applyPreferencesForPostVisibility(prevPrefs, savedInstanceState);
|
||||||
public void onSuccess(Preferences result){
|
}
|
||||||
// Only override the reply visibility if our preference is more private
|
AccountSessionManager.getInstance().getAccount(accountID).reloadPreferences(prefs->{
|
||||||
if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) {
|
applyPreferencesForPostVisibility(prefs, savedInstanceState);
|
||||||
// Map unlisted from the API onto public, because we don't have unlisted in the UI
|
});
|
||||||
statusVisibility = switch (result.postingDefaultVisibility) {
|
}
|
||||||
case PUBLIC, UNLISTED -> StatusPrivacy.PUBLIC;
|
|
||||||
case PRIVATE -> StatusPrivacy.PRIVATE;
|
|
||||||
case DIRECT -> StatusPrivacy.DIRECT;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// A saved privacy setting from a previous compose session wins over all
|
private void applyPreferencesForPostVisibility(Preferences prefs, Bundle savedInstanceState){
|
||||||
if(savedInstanceState !=null){
|
// Only override the reply visibility if our preference is more private
|
||||||
statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
if(prefs.postingDefaultVisibility.isLessVisibleThan(statusVisibility)){
|
||||||
}
|
// Map unlisted from the API onto public, because we don't have unlisted in the UI
|
||||||
|
statusVisibility=switch(prefs.postingDefaultVisibility){
|
||||||
|
case PUBLIC, UNLISTED -> StatusPrivacy.PUBLIC;
|
||||||
|
case PRIVATE -> StatusPrivacy.PRIVATE;
|
||||||
|
case DIRECT -> StatusPrivacy.DIRECT;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
updateVisibilityIcon ();
|
// A saved privacy setting from a previous compose session wins over all
|
||||||
}
|
if(savedInstanceState!=null){
|
||||||
|
statusVisibility=(StatusPrivacy) savedInstanceState.getSerializable("visibility");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
updateVisibilityIcon();
|
||||||
public void onError(ErrorResponse error){
|
|
||||||
Log.w(TAG, "Unable to get user preferences to set default post privacy");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exec(accountID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateVisibilityIcon(){
|
private void updateVisibilityIcon(){
|
||||||
|
@ -1037,4 +1045,19 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||||
public void addFakeMediaAttachment(Uri uri, String description){
|
public void addFakeMediaAttachment(Uri uri, String description){
|
||||||
mediaViewController.addFakeMediaAttachment(uri, description);
|
mediaViewController.addFakeMediaAttachment(uri, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showLanguageAlert(){
|
||||||
|
Preferences prefs=AccountSessionManager.getInstance().getAccount(accountID).preferences;
|
||||||
|
ComposeLanguageAlertViewController vc=new ComposeLanguageAlertViewController(getActivity(), prefs!=null ? prefs.postingDefaultLanguage : null, postLang, mainEditText.getText().toString());
|
||||||
|
new M3AlertDialogBuilder(getActivity())
|
||||||
|
.setTitle(R.string.language)
|
||||||
|
.setView(vc.getView())
|
||||||
|
.setPositiveButton(R.string.ok, (dialog, which)->setPostLanguage(vc.getSelectedOption()))
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPostLanguage(ComposeLanguageAlertViewController.SelectedOption language){
|
||||||
|
postLang=language;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
package org.joinmastodon.android.ui.viewcontrollers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.textclassifier.TextClassificationManager;
|
||||||
|
import android.view.textclassifier.TextLanguage;
|
||||||
|
import android.widget.Checkable;
|
||||||
|
import android.widget.CheckedTextView;
|
||||||
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.joinmastodon.android.api.MastodonAPIController;
|
||||||
|
import org.joinmastodon.android.ui.DividerItemDecoration;
|
||||||
|
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||||
|
import org.joinmastodon.android.ui.views.CheckableLinearLayout;
|
||||||
|
import org.parceler.Parcel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import me.grishka.appkit.utils.BindableViewHolder;
|
||||||
|
import me.grishka.appkit.utils.MergeRecyclerAdapter;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
import me.grishka.appkit.views.UsableRecyclerView;
|
||||||
|
|
||||||
|
public class ComposeLanguageAlertViewController{
|
||||||
|
private Context context;
|
||||||
|
private UsableRecyclerView list;
|
||||||
|
private List<LocaleInfo> allLocales;
|
||||||
|
private List<SpecialLocaleInfo> specialLocales=new ArrayList<>();
|
||||||
|
private int selectedIndex=0;
|
||||||
|
private Locale selectedLocale;
|
||||||
|
|
||||||
|
public ComposeLanguageAlertViewController(Context context, String preferred, SelectedOption previouslySelected, String postText){
|
||||||
|
this.context=context;
|
||||||
|
|
||||||
|
allLocales=Arrays.stream(Locale.getAvailableLocales())
|
||||||
|
.map(Locale::getLanguage)
|
||||||
|
.distinct()
|
||||||
|
.map(code->{
|
||||||
|
Locale l=Locale.forLanguageTag(code);
|
||||||
|
String name=l.getDisplayLanguage(Locale.getDefault());
|
||||||
|
return new LocaleInfo(l, capitalizeLanguageName(name));
|
||||||
|
})
|
||||||
|
.sorted(Comparator.comparing(a->a.displayName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if(!TextUtils.isEmpty(preferred)){
|
||||||
|
Locale l=Locale.forLanguageTag(preferred);
|
||||||
|
SpecialLocaleInfo pref=new SpecialLocaleInfo();
|
||||||
|
pref.locale=l;
|
||||||
|
pref.displayName=capitalizeLanguageName(l.getDisplayLanguage(Locale.getDefault()));
|
||||||
|
pref.title=context.getString(R.string.language_default);
|
||||||
|
specialLocales.add(pref);
|
||||||
|
}
|
||||||
|
|
||||||
|
Locale def=Locale.forLanguageTag(Locale.getDefault().getLanguage());
|
||||||
|
if(!def.getLanguage().equals(preferred)){
|
||||||
|
SpecialLocaleInfo d=new SpecialLocaleInfo();
|
||||||
|
d.locale=def;
|
||||||
|
d.displayName=capitalizeLanguageName(def.getDisplayName());
|
||||||
|
d.title=context.getString(R.string.language_system);
|
||||||
|
specialLocales.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Build.VERSION.SDK_INT>=29 && !TextUtils.isEmpty(postText)){
|
||||||
|
SpecialLocaleInfo detected=new SpecialLocaleInfo();
|
||||||
|
detected.displayName=context.getString(R.string.language_detecting);
|
||||||
|
detected.enabled=false;
|
||||||
|
specialLocales.add(detected);
|
||||||
|
detectLanguage(detected, postText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(previouslySelected!=null){
|
||||||
|
if((previouslySelected.index<specialLocales.size() && Objects.equals(previouslySelected.locale, specialLocales.get(previouslySelected.index).locale)) ||
|
||||||
|
(previouslySelected.index<specialLocales.size()+allLocales.size() && Objects.equals(previouslySelected.locale, allLocales.get(previouslySelected.index-specialLocales.size()).locale))){
|
||||||
|
selectedIndex=previouslySelected.index;
|
||||||
|
selectedLocale=previouslySelected.locale;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
selectedLocale=specialLocales.get(0).locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
list=new UsableRecyclerView(context);
|
||||||
|
MergeRecyclerAdapter adapter=new MergeRecyclerAdapter();
|
||||||
|
adapter.addAdapter(new SpecialLanguagesAdapter());
|
||||||
|
adapter.addAdapter(new AllLocalesAdapter());
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
list.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
|
||||||
|
list.addItemDecoration(new DividerItemDecoration(context, R.attr.colorM3OutlineVariant, 1, 16, 16, vh->vh.getAbsoluteAdapterPosition()==specialLocales.size()-1));
|
||||||
|
list.addItemDecoration(new RecyclerView.ItemDecoration(){
|
||||||
|
private Paint paint=new Paint();
|
||||||
|
|
||||||
|
{
|
||||||
|
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorM3OutlineVariant));
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeWidth(V.dp(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
|
||||||
|
if(parent.canScrollVertically(1)){
|
||||||
|
float y=parent.getHeight()-paint.getStrokeWidth()/2f;
|
||||||
|
c.drawLine(0, y, parent.getWidth(), y, paint);
|
||||||
|
}
|
||||||
|
if(parent.canScrollVertically(-1)){
|
||||||
|
float y=paint.getStrokeWidth()/2f;
|
||||||
|
c.drawLine(0, y, parent.getWidth(), y, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(previouslySelected!=null && selectedIndex>0){
|
||||||
|
list.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw(){
|
||||||
|
list.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
|
||||||
|
if(list.findViewHolderForAdapterPosition(selectedIndex)==null)
|
||||||
|
list.scrollToPosition(selectedIndex);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||||
|
private void detectLanguage(SpecialLocaleInfo info, String text){
|
||||||
|
MastodonAPIController.runInBackground(()->{
|
||||||
|
TextLanguage lang=context.getSystemService(TextClassificationManager.class).getTextClassifier().detectLanguage(new TextLanguage.Request.Builder(text).build());
|
||||||
|
list.post(()->{
|
||||||
|
SpecialLanguageViewHolder holder=(SpecialLanguageViewHolder) list.findViewHolderForAdapterPosition(specialLocales.indexOf(info));
|
||||||
|
if(lang.getLocaleHypothesisCount()==0 || lang.getConfidenceScore(lang.getLocale(0))<0.75f){
|
||||||
|
info.displayName=context.getString(R.string.language_cant_detect);
|
||||||
|
}else{
|
||||||
|
Locale locale=lang.getLocale(0).toLocale();
|
||||||
|
info.locale=locale;
|
||||||
|
info.displayName=capitalizeLanguageName(locale.getDisplayName(Locale.getDefault()));
|
||||||
|
info.title=context.getString(R.string.language_detected);
|
||||||
|
info.enabled=true;
|
||||||
|
if(holder!=null)
|
||||||
|
UiUtils.beginLayoutTransition(holder.view);
|
||||||
|
}
|
||||||
|
if(holder!=null)
|
||||||
|
holder.rebind();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public View getView(){
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needed because in some languages (e.g. Slavic ones) these names returned by the system start with a lowercase letter
|
||||||
|
private String capitalizeLanguageName(String name){
|
||||||
|
return name.substring(0, 1).toUpperCase(Locale.getDefault())+name.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectedOption getSelectedOption(){
|
||||||
|
return new SelectedOption(selectedIndex, selectedLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectItem(int index){
|
||||||
|
if(index==selectedIndex)
|
||||||
|
return;
|
||||||
|
if(selectedIndex!=-1){
|
||||||
|
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(selectedIndex);
|
||||||
|
if(holder!=null && holder.itemView instanceof Checkable checkable)
|
||||||
|
checkable.setChecked(false);
|
||||||
|
}
|
||||||
|
RecyclerView.ViewHolder holder=list.findViewHolderForAdapterPosition(index);
|
||||||
|
if(holder!=null && holder.itemView instanceof Checkable checkable)
|
||||||
|
checkable.setChecked(true);
|
||||||
|
selectedIndex=index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AllLocalesAdapter extends RecyclerView.Adapter<SimpleLanguageViewHolder>{
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SimpleLanguageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new SimpleLanguageViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SimpleLanguageViewHolder holder, int position){
|
||||||
|
holder.bind(allLocales.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return allLocales.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SimpleLanguageViewHolder extends BindableViewHolder<LocaleInfo> implements UsableRecyclerView.Clickable{
|
||||||
|
private final CheckedTextView text;
|
||||||
|
|
||||||
|
public SimpleLanguageViewHolder(){
|
||||||
|
super(context, R.layout.item_alert_single_choice_1line, list);
|
||||||
|
text=(CheckedTextView) itemView;
|
||||||
|
text.setCompoundDrawablesRelativeWithIntrinsicBounds(new RadioButton(context).getButtonDrawable(), null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(LocaleInfo item){
|
||||||
|
text.setText(item.displayName);
|
||||||
|
text.setChecked(selectedIndex==getAbsoluteAdapterPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
selectItem(getAbsoluteAdapterPosition());
|
||||||
|
selectedLocale=item.locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SpecialLanguagesAdapter extends RecyclerView.Adapter<SpecialLanguageViewHolder>{
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SpecialLanguageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){
|
||||||
|
return new SpecialLanguageViewHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SpecialLanguageViewHolder holder, int position){
|
||||||
|
holder.bind(specialLocales.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount(){
|
||||||
|
return specialLocales.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position){
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SpecialLanguageViewHolder extends BindableViewHolder<SpecialLocaleInfo> implements UsableRecyclerView.DisableableClickable{
|
||||||
|
private final TextView text, title;
|
||||||
|
private final CheckableLinearLayout view;
|
||||||
|
|
||||||
|
public SpecialLanguageViewHolder(){
|
||||||
|
super(context, R.layout.item_alert_single_choice_2lines, list);
|
||||||
|
text=findViewById(R.id.text);
|
||||||
|
title=findViewById(R.id.title);
|
||||||
|
view=((CheckableLinearLayout) itemView);
|
||||||
|
findViewById(R.id.radiobutton).setBackground(new RadioButton(context).getButtonDrawable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBind(SpecialLocaleInfo item){
|
||||||
|
text.setText(item.displayName);
|
||||||
|
if(!TextUtils.isEmpty(item.title)){
|
||||||
|
title.setVisibility(View.VISIBLE);
|
||||||
|
title.setText(item.title);
|
||||||
|
}else{
|
||||||
|
title.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
text.setEnabled(item.enabled);
|
||||||
|
view.setEnabled(item.enabled);
|
||||||
|
view.setChecked(selectedIndex==getAbsoluteAdapterPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(){
|
||||||
|
selectItem(getAbsoluteAdapterPosition());
|
||||||
|
selectedLocale=item.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled(){
|
||||||
|
return item.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LocaleInfo{
|
||||||
|
public final Locale locale;
|
||||||
|
public final String displayName;
|
||||||
|
|
||||||
|
private LocaleInfo(Locale locale, String displayName){
|
||||||
|
this.locale=locale;
|
||||||
|
this.displayName=displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SpecialLocaleInfo{
|
||||||
|
public Locale locale;
|
||||||
|
public String displayName;
|
||||||
|
public String title;
|
||||||
|
public boolean enabled=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcel
|
||||||
|
public static class SelectedOption{
|
||||||
|
public int index;
|
||||||
|
public Locale locale;
|
||||||
|
|
||||||
|
public SelectedOption(){}
|
||||||
|
|
||||||
|
public SelectedOption(int index, Locale locale){
|
||||||
|
this.index=index;
|
||||||
|
this.locale=locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:color="?colorM3OnSurface" android:state_enabled="true"/>
|
||||||
|
<item android:color="?colorM3Secondary"/>
|
||||||
|
</selector>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,22Q9.95,22 8.125,21.212Q6.3,20.425 4.938,19.062Q3.575,17.7 2.788,15.875Q2,14.05 2,12Q2,9.925 2.788,8.113Q3.575,6.3 4.938,4.938Q6.3,3.575 8.125,2.787Q9.95,2 12,2Q14.075,2 15.887,2.787Q17.7,3.575 19.062,4.938Q20.425,6.3 21.212,8.113Q22,9.925 22,12Q22,14.05 21.212,15.875Q20.425,17.7 19.062,19.062Q17.7,20.425 15.887,21.212Q14.075,22 12,22ZM15.95,8H18.9Q18.175,6.75 17.087,5.825Q16,4.9 14.6,4.45Q15.05,5.275 15.388,6.162Q15.725,7.05 15.95,8ZM10.1,8H13.9Q13.6,6.9 13.125,5.925Q12.65,4.95 12,4.05Q11.35,4.95 10.875,5.925Q10.4,6.9 10.1,8ZM4.25,14H7.65Q7.575,13.5 7.537,13.012Q7.5,12.525 7.5,12Q7.5,11.475 7.537,10.988Q7.575,10.5 7.65,10H4.25Q4.125,10.5 4.062,10.988Q4,11.475 4,12Q4,12.525 4.062,13.012Q4.125,13.5 4.25,14ZM9.4,19.55Q8.95,18.725 8.613,17.837Q8.275,16.95 8.05,16H5.1Q5.825,17.25 6.912,18.175Q8,19.1 9.4,19.55ZM5.1,8H8.05Q8.275,7.05 8.613,6.162Q8.95,5.275 9.4,4.45Q8,4.9 6.912,5.825Q5.825,6.75 5.1,8ZM12,19.95Q12.65,19.05 13.125,18.075Q13.6,17.1 13.9,16H10.1Q10.4,17.1 10.875,18.075Q11.35,19.05 12,19.95ZM9.65,14H14.35Q14.425,13.5 14.463,13.012Q14.5,12.525 14.5,12Q14.5,11.475 14.463,10.988Q14.425,10.5 14.35,10H9.65Q9.575,10.5 9.538,10.988Q9.5,11.475 9.5,12Q9.5,12.525 9.538,13.012Q9.575,13.5 9.65,14ZM14.6,19.55Q16,19.1 17.087,18.175Q18.175,17.25 18.9,16H15.95Q15.725,16.95 15.388,17.837Q15.05,18.725 14.6,19.55ZM16.35,14H19.75Q19.875,13.5 19.938,13.012Q20,12.525 20,12Q20,11.475 19.938,10.988Q19.875,10.5 19.75,10H16.35Q16.425,10.5 16.462,10.988Q16.5,11.475 16.5,12Q16.5,12.525 16.462,13.012Q16.425,13.5 16.35,14Z"/>
|
||||||
|
</vector>
|
|
@ -388,6 +388,19 @@
|
||||||
android:tooltipText="@string/content_warning"
|
android:tooltipText="@string/content_warning"
|
||||||
android:src="@drawable/ic_compose_cw"/>
|
android:src="@drawable/ic_compose_cw"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_language"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:background="@drawable/bg_compose_button"
|
||||||
|
android:padding="0px"
|
||||||
|
android:tint="@color/compose_button"
|
||||||
|
android:tintMode="src_in"
|
||||||
|
android:contentDescription="@string/language"
|
||||||
|
android:tooltipText="@string/language"
|
||||||
|
android:src="@drawable/ic_language_24px"/>
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:layout_width="0px"
|
android:layout_width="0px"
|
||||||
android:layout_height="1px"
|
android:layout_height="1px"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:drawablePadding="12dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:textColor="?colorM3OnSurface"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="Item text">
|
||||||
|
|
||||||
|
</CheckedTextView>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.joinmastodon.android.ui.views.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="24dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/radiobutton"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:duplicateParentState="true"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:textAppearance="@style/m3_label_medium"
|
||||||
|
android:textColor="?colorM3OnSurfaceVariant"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="Title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:textAppearance="@style/m3_body_large"
|
||||||
|
android:textColor="@color/text_on_surface_disableable"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="end"
|
||||||
|
tools:text="Text"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</org.joinmastodon.android.ui.views.CheckableLinearLayout>
|
|
@ -484,4 +484,10 @@
|
||||||
<string name="compose_autocomplete_emoji_empty">Browse emoji</string>
|
<string name="compose_autocomplete_emoji_empty">Browse emoji</string>
|
||||||
<string name="compose_autocomplete_users_empty">Find who you\'re looking for</string>
|
<string name="compose_autocomplete_users_empty">Find who you\'re looking for</string>
|
||||||
<string name="no_search_results">Could not find anything for these search terms</string>
|
<string name="no_search_results">Could not find anything for these search terms</string>
|
||||||
|
<string name="language">Language</string>
|
||||||
|
<string name="language_default">Default</string>
|
||||||
|
<string name="language_system">System</string>
|
||||||
|
<string name="language_detecting">Detecting language</string>
|
||||||
|
<string name="language_cant_detect">Unable to detect language</string>
|
||||||
|
<string name="language_detected">Detected</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue