From 4e6ba84bb3a33e80fc415a2082c513d47e48cd17 Mon Sep 17 00:00:00 2001 From: sk Date: Fri, 9 Dec 2022 19:34:43 +0100 Subject: [PATCH 1/3] implement language selector --- .../android/fragments/ComposeFragment.java | 89 ++++++++++++++++++- mastodon/src/main/res/values/strings.xml | 2 + 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 406d74673..00e264a8a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -7,6 +7,7 @@ import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Outline; @@ -19,6 +20,7 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.LocaleList; import android.os.Parcelable; import android.provider.OpenableColumns; import android.text.Editable; @@ -28,11 +30,13 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; +import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; @@ -101,6 +105,8 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.UUID; @@ -128,6 +134,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr // from https://github.com/mastodon/mastodon-ios/blob/main/Mastodon/Helper/MastodonRegex.swift private static final Pattern AUTO_COMPLETE_PATTERN=Pattern.compile("(? allIsoLanguages; @SuppressLint("NewApi") // this class actually exists on 6.0 private final BreakIterator breakIterator=BreakIterator.getCharacterInstance(); @@ -143,7 +150,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr private String accountID; private int charCount, charLimit, trimmedCharCount; - private Button publishButton; + private Button publishButton, languageButton; + private PopupMenu languagePopup; private ImageButton mediaBtn, pollBtn, emojiBtn, spoilerBtn, visibilityBtn; private ComposeMediaLayout attachmentsView; private TextView replyText; @@ -182,6 +190,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr private boolean ignoreSelectionChanges=false; private Runnable updateUploadEtaRunnable; + private String language; + + static { + Locale[] locales = Locale.getAvailableLocales(); + List allLocales = new ArrayList<>(); + List addedLanguages = new ArrayList<>(); + for (Locale locale : locales) { + String lang = locale.getLanguage(); + if (!addedLanguages.contains(lang)) { + allLocales.add(locale); + addedLanguages.add(lang); + } + } + + Collections.sort(allLocales, Comparator.comparing(l -> l.getDisplayLanguage(Locale.getDefault()))); + allIsoLanguages = allLocales; + } + @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -508,6 +534,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr ignoreSelectionChanges=true; mainEditText.setSelection(mainEditText.length()); ignoreSelectionChanges=false; + updateLanguage(new Locale(editingStatus.language)); if(!editingStatus.mediaAttachments.isEmpty()){ attachmentsView.setVisibility(View.VISIBLE); for(Attachment att:editingStatus.mediaAttachments){ @@ -568,6 +595,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr sendError.setVisibility(View.GONE); sendProgress.setVisibility(View.GONE); + LinearLayout.LayoutParams langParams=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + langParams.setMarginEnd(V.dp(8)); + wrap.addView(buildLanguageSelector(), langParams); + wrap.addView(publishButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8)); wrap.setClipToPadding(false); @@ -577,6 +608,61 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr updatePublishButtonState(); } + private void updateLanguage(Locale loc) { + language = loc.getLanguage(); + languageButton.setText(loc.getDisplayLanguage(loc)); + languageButton.setContentDescription(getActivity().getString(R.string.post_language, loc.getDisplayLanguage(Locale.getDefault()))); + } + + @SuppressLint("ClickableViewAccessibility") + private Button buildLanguageSelector() { + languageButton=new Button(getActivity()); + TypedValue typedValue = new TypedValue(); + getActivity().getTheme().resolveAttribute(android.R.attr.textColorSecondary, typedValue, true); + languageButton.setTextColor(typedValue.data); + languageButton.setBackground(getActivity().getDrawable(R.drawable.bg_text_button)); + languageButton.setPadding(V.dp(8), 0, V.dp(8), 0); + languageButton.setCompoundDrawablesRelativeWithIntrinsicBounds(getActivity().getDrawable(R.drawable.ic_fluent_local_language_16_regular), null, null, null); + languageButton.setCompoundDrawableTintList(languageButton.getTextColors()); + languageButton.setCompoundDrawablePadding(V.dp(6)); + + updateLanguage(Locale.getDefault()); + languagePopup=new PopupMenu(getActivity(), languageButton); + languageButton.setOnTouchListener(languagePopup.getDragToOpenListener()); + languageButton.setOnClickListener(v->languagePopup.show()); + + Menu languageMenu = languagePopup.getMenu(); + + // TODO: add recently used languages + + List systemLocales = new ArrayList<>();; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + systemLocales.add(Resources.getSystem().getConfiguration().locale); + } else { + LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); + for (int i = 0; i < localeList.size(); i++) systemLocales.add(localeList.get(i)); + } + + for (int i = 0; i < systemLocales.size(); i++) { + Locale loc = new Locale(systemLocales.get(i).getLanguage()); + languageMenu.add(0, allIsoLanguages.indexOf(loc), Menu.NONE, loc.getDisplayLanguage(Locale.getDefault())); + } + + SubMenu allLanguagesMenu = languageMenu.addSubMenu(0, -1, Menu.NONE, R.string.all_languages); + for (int i = 0; i < allIsoLanguages.size(); i++) { + Locale loc = allIsoLanguages.get(i); + allLanguagesMenu.add(0, i, Menu.NONE, loc.getDisplayLanguage(Locale.getDefault())); + } + + languagePopup.setOnMenuItemClickListener(i->{ + if (i.hasSubMenu()) return false; + updateLanguage(allIsoLanguages.get(i.getItemId())); + return true; + }); + + return languageButton; + } + @Override public boolean onOptionsItemSelected(MenuItem item){ return true; @@ -649,6 +735,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr CreateStatus.Request req=new CreateStatus.Request(); req.status=text; req.visibility=statusVisibility; + req.language=language; if(!attachments.isEmpty()){ req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList()); } diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index aeed6dfed..2c89ed9da 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -405,4 +405,6 @@ Your handle might be @gothgirl654@example.social, but you can still follow, reblog, and chat with @fallout5ever@example.online. How do I pick a server? Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.\n\nYou can’t go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), you’ll never miss a beat anywhere. + Language: %s + All languages \ No newline at end of file From 9667a32e44ede1dc2b94b6c367748f992b8bc2bb Mon Sep 17 00:00:00 2001 From: sk Date: Fri, 9 Dec 2022 20:48:51 +0100 Subject: [PATCH 2/3] save recently used languages --- .../android/GlobalUserPreferences.java | 25 +++++++++++++++++++ .../android/fragments/ComposeFragment.java | 25 ++++++++----------- .../android/fragments/SettingsFragment.java | 4 +++ mastodon/src/main/res/values/strings.xml | 2 ++ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index 5ffa48d54..f46d0e832 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -2,12 +2,35 @@ package org.joinmastodon.android; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Build; +import android.os.LocaleList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; public class GlobalUserPreferences{ public static boolean playGifs; public static boolean useCustomTabs; public static boolean trueBlackTheme; public static ThemePreference theme; + public static List recentLanguages; + + private static String defaultRecentLanguages; + + static { + List systemLocales = new ArrayList<>();; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + systemLocales.add(Resources.getSystem().getConfiguration().locale); + } else { + LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); + for (int i = 0; i < localeList.size(); i++) systemLocales.add(localeList.get(i)); + } + + defaultRecentLanguages = systemLocales.stream().map(Locale::getLanguage).collect(Collectors.joining(",")); + } private static SharedPreferences getPrefs(){ return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE); @@ -19,6 +42,7 @@ public class GlobalUserPreferences{ useCustomTabs=prefs.getBoolean("useCustomTabs", true); trueBlackTheme=prefs.getBoolean("trueBlackTheme", false); theme=ThemePreference.values()[prefs.getInt("theme", 0)]; + recentLanguages=Arrays.stream(prefs.getString("recentLanguages", defaultRecentLanguages).split(",")).filter(s->!s.isEmpty()).collect(Collectors.toList()); } public static void save(){ @@ -27,6 +51,7 @@ public class GlobalUserPreferences{ .putBoolean("useCustomTabs", useCustomTabs) .putBoolean("trueBlackTheme", trueBlackTheme) .putInt("theme", theme.ordinal()) + .putString("recentLanguages", String.join(",", recentLanguages)) .apply(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 00e264a8a..b0dc03654 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -7,7 +7,6 @@ import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Outline; @@ -20,7 +19,6 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.LocaleList; import android.os.Parcelable; import android.provider.OpenableColumns; import android.text.Editable; @@ -57,6 +55,7 @@ import android.widget.Toast; import com.twitter.twittertext.TwitterTextEmojiRegex; import org.joinmastodon.android.E; +import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIController; @@ -633,22 +632,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr Menu languageMenu = languagePopup.getMenu(); - // TODO: add recently used languages - - List systemLocales = new ArrayList<>();; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - systemLocales.add(Resources.getSystem().getConfiguration().locale); - } else { - LocaleList localeList = Resources.getSystem().getConfiguration().getLocales(); - for (int i = 0; i < localeList.size(); i++) systemLocales.add(localeList.get(i)); - } - - for (int i = 0; i < systemLocales.size(); i++) { - Locale loc = new Locale(systemLocales.get(i).getLanguage()); + for (String recentLanguage : GlobalUserPreferences.recentLanguages) { + Locale loc = new Locale(recentLanguage); languageMenu.add(0, allIsoLanguages.indexOf(loc), Menu.NONE, loc.getDisplayLanguage(Locale.getDefault())); } - SubMenu allLanguagesMenu = languageMenu.addSubMenu(0, -1, Menu.NONE, R.string.all_languages); + SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.all_languages); for (int i = 0; i < allIsoLanguages.size(); i++) { Locale loc = allIsoLanguages.get(i); allLanguagesMenu.add(0, i, Menu.NONE, loc.getDisplayLanguage(Locale.getDefault())); @@ -805,6 +794,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr .setCallback(resCallback) .exec(accountID); } + + List recentLanguages = new ArrayList<>(GlobalUserPreferences.recentLanguages); + recentLanguages.remove(language); + recentLanguages.add(0, language); + GlobalUserPreferences.recentLanguages = recentLanguages.stream().limit(4).collect(Collectors.toList()); + GlobalUserPreferences.save(); } private boolean hasDraft(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java index cc642d9a5..e98a6107d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java @@ -120,6 +120,10 @@ public class SettingsFragment extends MastodonToolbarFragment{ items.add(new RedHeaderItem(R.string.settings_spicy)); items.add(new TextItem(R.string.settings_clear_cache, this::clearImageCache)); + items.add(new TextItem(R.string.clear_recent_languages, ()->UiUtils.showConfirmationAlert(getActivity(), R.string.clear_recent_languages, R.string.confirm_clear_recent_languages, R.string.clear, ()->{ + GlobalUserPreferences.recentLanguages.clear(); + GlobalUserPreferences.save(); + }))); items.add(new TextItem(R.string.log_out, this::confirmLogOut)); items.add(new FooterItem(getString(R.string.settings_app_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE))); diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index 2c89ed9da..b3b3a0ba1 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -407,4 +407,6 @@ Different people choose different servers for any number of reasons. art.example is a great place for artists, while glasgow.example might be a good pick for Scots.\n\nYou can’t go wrong with any of our recommend servers, so regardless of which one you pick (or if you enter your own in the server search bar), you’ll never miss a beat anywhere. Language: %s All languages + Clear recent languages + Are you sure you want to clear your recently used languages? \ No newline at end of file From 17dc0850d572c3f5709d7bc7d663f25b1c2f2541 Mon Sep 17 00:00:00 2001 From: sk Date: Fri, 9 Dec 2022 20:53:56 +0100 Subject: [PATCH 3/3] apply language when replying --- .../joinmastodon/android/fragments/ComposeFragment.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index b0dc03654..0ed346baf 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -408,6 +408,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr outState.putString("pollDurationStr", pollDurationStr); } outState.putBoolean("hasSpoiler", hasSpoiler); + outState.putString("language", language); if(!attachments.isEmpty()){ ArrayList serializedAttachments=new ArrayList<>(attachments.size()); for(DraftMediaAttachment att:attachments){ @@ -522,6 +523,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr spoilerEdit.setText(replyTo.spoilerText); spoilerBtn.setSelected(true); } + if (replyTo.language != null && !replyTo.language.isEmpty()) updateLanguage(replyTo.language); } }else{ replyText.setVisibility(View.GONE); @@ -533,7 +535,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr ignoreSelectionChanges=true; mainEditText.setSelection(mainEditText.length()); ignoreSelectionChanges=false; - updateLanguage(new Locale(editingStatus.language)); + updateLanguage(editingStatus.language); if(!editingStatus.mediaAttachments.isEmpty()){ attachmentsView.setVisibility(View.VISIBLE); for(Attachment att:editingStatus.mediaAttachments){ @@ -607,6 +609,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr updatePublishButtonState(); } + private void updateLanguage(String lang) { + updateLanguage(new Locale(lang)); + } + private void updateLanguage(Locale loc) { language = loc.getLanguage(); languageButton.setText(loc.getDisplayLanguage(loc));