diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index 9bdf9dcdb..82acd5ab1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -1,8 +1,18 @@ package org.joinmastodon.android; +import static org.joinmastodon.android.api.MastodonAPIController.gson; + import android.content.Context; import android.content.SharedPreferences; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class GlobalUserPreferences{ public static boolean playGifs; public static boolean useCustomTabs; @@ -18,10 +28,18 @@ public class GlobalUserPreferences{ public static ThemePreference theme; public static ColorPreference color; + private final static Type recentLanguagesType = new TypeToken>>() {}.getType(); + public static Map> recentLanguages; + private static SharedPreferences getPrefs(){ return MastodonApp.context.getSharedPreferences("global", Context.MODE_PRIVATE); } + private static T fromJson(String json, Type type, T orElse) { + try { return gson.fromJson(json, type); } + catch (JsonSyntaxException ignored) { return orElse; } + } + public static void load(){ SharedPreferences prefs=getPrefs(); playGifs=prefs.getBoolean("playGifs", true); @@ -36,6 +54,7 @@ public class GlobalUserPreferences{ disableMarquee=prefs.getBoolean("disableMarquee", false); voteButtonForSingleChoice=prefs.getBoolean("voteButtonForSingleChoice", true); theme=ThemePreference.values()[prefs.getInt("theme", 0)]; + recentLanguages=fromJson(prefs.getString("recentLanguages", "{}"), recentLanguagesType, new HashMap<>()); color=ColorPreference.values()[prefs.getInt("color", 1)]; } @@ -52,6 +71,7 @@ public class GlobalUserPreferences{ .putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings) .putBoolean("disableMarquee", disableMarquee) .putInt("theme", theme.ordinal()) + .putString("recentLanguages", gson.toJson(recentLanguages)) .putInt("color", color.ordinal()) .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 d40b4ec2b..b6e5ad690 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -1,5 +1,9 @@ package org.joinmastodon.android.fragments; +import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages; +import static org.joinmastodon.android.utils.MastodonLanguage.allLanguages; +import static org.joinmastodon.android.utils.MastodonLanguage.defaultRecentLanguages; + import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; @@ -29,11 +33,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; @@ -55,6 +61,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; @@ -95,6 +102,7 @@ import org.joinmastodon.android.ui.views.ComposeEditText; import org.joinmastodon.android.ui.views.ComposeMediaLayout; import org.joinmastodon.android.ui.views.ReorderableLinearLayout; import org.joinmastodon.android.ui.views.SizeListenerLinearLayout; +import org.joinmastodon.android.utils.MastodonLanguage; import org.parceler.Parcel; import org.parceler.Parcels; @@ -105,6 +113,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -145,7 +154,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 ImageView sensitiveIcon; private ComposeMediaLayout attachmentsView; @@ -190,6 +200,9 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr private boolean ignoreSelectionChanges=false; private Runnable updateUploadEtaRunnable; + private String language; + private MastodonLanguage.LanguageResolver languageResolver; + @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -201,6 +214,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr instanceDomain=session.domain; customEmojis=AccountSessionManager.getInstance().getCustomEmojis(instanceDomain); instance=AccountSessionManager.getInstance().getInstanceInfo(instanceDomain); + languageResolver=new MastodonLanguage.LanguageResolver(instance); if(getArguments().containsKey("editStatus")){ editingStatus=Parcels.unwrap(getArguments().getParcelable("editStatus")); redraftStatus=getArguments().getBoolean("redraftStatus"); @@ -403,6 +417,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr } outState.putBoolean("sensitive", sensitive); outState.putBoolean("hasSpoiler", hasSpoiler); + outState.putString("language", language); if(!attachments.isEmpty()){ ArrayList serializedAttachments=new ArrayList<>(attachments.size()); for(DraftMediaAttachment att:attachments){ @@ -542,6 +557,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); @@ -553,6 +569,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr ignoreSelectionChanges=true; mainEditText.setSelection(mainEditText.length()); ignoreSelectionChanges=false; + updateLanguage(editingStatus.language); if(!editingStatus.mediaAttachments.isEmpty()){ attachmentsView.setVisibility(View.VISIBLE); for(Attachment att:editingStatus.mediaAttachments){ @@ -615,6 +632,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); @@ -624,6 +645,55 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr updatePublishButtonState(); } + private void updateLanguage(String lang) { + updateLanguage(languageResolver.from(lang)); + } + + private void updateLanguage(MastodonLanguage loc) { + language = loc.getLanguage(); + languageButton.setText(loc.getLanguageName()); + languageButton.setContentDescription(getActivity().getString(R.string.post_language, loc.getDefaultName())); + } + + @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(languageResolver.getDefault()); + languagePopup=new PopupMenu(getActivity(), languageButton); + languageButton.setOnTouchListener(languagePopup.getDragToOpenListener()); + languageButton.setOnClickListener(v->languagePopup.show()); + + Menu languageMenu = languagePopup.getMenu(); + + for (String recentLanguage : Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)) { + MastodonLanguage l = languageResolver.from(recentLanguage); + languageMenu.add(0, allLanguages.indexOf(l), Menu.NONE, getActivity().getString(R.string.language_name, l.getDefaultName(), l.getLanguageName())); + } + + SubMenu allLanguagesMenu = languageMenu.addSubMenu(R.string.available_languages); + for (int i = 0; i < allLanguages.size(); i++) { + MastodonLanguage l = allLanguages.get(i); + allLanguagesMenu.add(0, i, Menu.NONE, getActivity().getString(R.string.language_name, l.getDefaultName(), l.getLanguageName())); + } + + languagePopup.setOnMenuItemClickListener(i->{ + if (i.hasSubMenu()) return false; + updateLanguage(allLanguages.get(i.getItemId())); + return true; + }); + + return languageButton; + } + @Override public boolean onOptionsItemSelected(MenuItem item){ return true; @@ -697,6 +767,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr req.status=text; req.visibility=statusVisibility; req.sensitive=sensitive; + req.language=language; if(!attachments.isEmpty()){ req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList()); } @@ -773,6 +844,14 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr .setCallback(resCallback) .exec(accountID); } + + if (replyTo == null) { + List newRecentLanguages = new ArrayList<>(Optional.ofNullable(recentLanguages.get(accountID)).orElse(defaultRecentLanguages)); + newRecentLanguages.remove(language); + newRecentLanguages.add(0, language); + recentLanguages.put(accountID, newRecentLanguages.stream().limit(4).collect(Collectors.toList())); + GlobalUserPreferences.save(); + } } private boolean hasDraft(){ @@ -1362,6 +1441,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr public void onSuccess(Preferences result){ // Only override the reply visibility if our preference is more private if (result.postingDefaultVisibility.isLessVisibleThan(statusVisibility)) { + // Map unlisted from the API onto public, because we don't have unlisted in the UI statusVisibility = switch (result.postingDefaultVisibility) { case PUBLIC -> StatusPrivacy.PUBLIC; case UNLISTED -> StatusPrivacy.UNLISTED; 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 844e33955..c067fb0b2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java @@ -48,6 +48,7 @@ import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.updater.GithubSelfUpdater; import java.util.ArrayList; +import java.util.Objects; import java.util.function.Consumer; import androidx.annotation.DrawableRes; @@ -159,6 +160,10 @@ public class SettingsFragment extends MastodonToolbarFragment{ } items.add(new TextItem(R.string.settings_contribute_fork, ()->UiUtils.launchWebBrowser(getActivity(), "https://github.com/LucasGGamerM/moshidon"))); 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.remove(accountID); + 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/java/org/joinmastodon/android/fragments/SplashFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java index 8f607e629..8b222c6dc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SplashFragment.java @@ -1,11 +1,19 @@ package org.joinmastodon.android.fragments; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.SpannableString; +import android.text.style.ImageSpan; +import android.text.style.ReplacementSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowInsets; +import android.widget.LinearLayout; +import android.widget.TextView; import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; @@ -14,7 +22,11 @@ import org.joinmastodon.android.fragments.onboarding.InstanceChooserLoginFragmen import org.joinmastodon.android.ui.InterpolatingMotionEffect; import org.joinmastodon.android.ui.views.SizeListenerFrameLayout; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager.widget.PagerAdapter; +import androidx.viewpager2.widget.ViewPager2; import me.grishka.appkit.Nav; import me.grishka.appkit.fragments.AppKitFragment; import me.grishka.appkit.utils.V; @@ -23,12 +35,13 @@ public class SplashFragment extends AppKitFragment{ private SizeListenerFrameLayout contentView; private View artContainer, blueFill, greenFill; - private InterpolatingMotionEffect motionEffect; + private ViewPager2 pager; + private ViewGroup pagerDots; + private View artClouds, artPlaneElephant, artRightHill, artLeftHill, artCenterHill; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); - motionEffect=new InterpolatingMotionEffect(MastodonApp.context); } @Nullable @@ -37,15 +50,44 @@ public class SplashFragment extends AppKitFragment{ contentView=(SizeListenerFrameLayout) inflater.inflate(R.layout.fragment_splash, container, false); contentView.findViewById(R.id.btn_get_started).setOnClickListener(this::onButtonClick); contentView.findViewById(R.id.btn_log_in).setOnClickListener(this::onButtonClick); + artClouds=contentView.findViewById(R.id.art_clouds); + artPlaneElephant=contentView.findViewById(R.id.art_plane_elephant); + artRightHill=contentView.findViewById(R.id.art_right_hill); + artLeftHill=contentView.findViewById(R.id.art_left_hill); + artCenterHill=contentView.findViewById(R.id.art_center_hill); + pager=contentView.findViewById(R.id.pager); + pagerDots=contentView.findViewById(R.id.pager_dots); + pager.setAdapter(new PagerAdapter()); + pager.setOffscreenPageLimit(3); + pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback(){ + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){ + for(int i=0;i=1 ? 1f : positionOffset)); + artPlaneElephant.setTranslationX(V.dp(101.55f)*parallaxProgress); + artLeftHill.setTranslationX(V.dp(-88)*parallaxProgress); + artLeftHill.setTranslationY(V.dp(24)*parallaxProgress); + artRightHill.setTranslationX(V.dp(-88)*parallaxProgress); + artRightHill.setTranslationY(V.dp(-24)*parallaxProgress); + artCenterHill.setTranslationX(V.dp(-40)*parallaxProgress); + } + }); artContainer=contentView.findViewById(R.id.art_container); blueFill=contentView.findViewById(R.id.blue_fill); greenFill=contentView.findViewById(R.id.green_fill); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_clouds), V.dp(-5), V.dp(5), V.dp(-5), V.dp(5))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_right_hill), V.dp(-15), V.dp(25), V.dp(-10), V.dp(10))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_left_hill), V.dp(-25), V.dp(15), V.dp(-15), V.dp(15))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_center_hill), V.dp(-14), V.dp(14), V.dp(-5), V.dp(25))); - motionEffect.addViewEffect(new InterpolatingMotionEffect.ViewEffect(contentView.findViewById(R.id.art_plane_elephant), V.dp(-20), V.dp(12), V.dp(-20), V.dp(12))); contentView.setSizeListener(new SizeListenerFrameLayout.OnSizeChangedListener(){ @Override @@ -72,10 +114,10 @@ public class SplashFragment extends AppKitFragment{ } private void updateArtSize(int w, int h){ - float scale=w/(float)V.dp(412); + float scale=w/(float)V.dp(360); artContainer.setScaleX(scale); artContainer.setScaleY(scale); - blueFill.setScaleY(h/2f); + blueFill.setScaleY(artContainer.getBottom()-V.dp(90)); greenFill.setScaleY(h-artContainer.getBottom()+V.dp(90)); } @@ -101,15 +143,91 @@ public class SplashFragment extends AppKitFragment{ return true; } - @Override - protected void onShown(){ - super.onShown(); - motionEffect.activate(); + private class PagerAdapter extends RecyclerView.Adapter{ + + @NonNull + @Override + public PagerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType){ + return new PagerViewHolder(viewType); + } + + @Override + public void onBindViewHolder(@NonNull PagerViewHolder holder, int position){} + + @Override + public int getItemCount(){ + return 3; + } + + @Override + public int getItemViewType(int position){ + return position; + } } - @Override - protected void onHidden(){ - super.onHidden(); - motionEffect.deactivate(); + private class PagerViewHolder extends RecyclerView.ViewHolder{ + public PagerViewHolder(int page){ + super(new LinearLayout(getActivity())); + LinearLayout ll=(LinearLayout) itemView; + ll.setOrientation(LinearLayout.VERTICAL); + int pad=V.dp(16); + ll.setPadding(pad, pad, pad, pad); + ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + TextView title=new TextView(getActivity()); + title.setTextAppearance(R.style.m3_headline_medium); + title.setText(switch(page){ + case 0 -> { + String src=getString(R.string.welcome_page1_title); + SpannableString ss=new SpannableString(src); + int start=src.indexOf("{logo}"); + if(start!=-1){ + LogoSpan span=new LogoSpan(getResources().getDrawable(R.drawable.splash_logo, getActivity().getTheme())); + ss.setSpan(span, start, start+6, 0); + } + yield ss; + } + case 1 -> getString(R.string.welcome_page2_title); + case 2 -> getString(R.string.welcome_page3_title); + default -> throw new IllegalStateException("Unexpected value: "+page); + }); + title.setTextColor(0xFF17063B); + LinearLayout.LayoutParams lp=new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, V.dp(page==0 ? 46 : 36)); + lp.bottomMargin=V.dp(page==0 ? 4 : 14); + ll.addView(title, lp); + + TextView text=new TextView(getActivity()); + text.setTextAppearance(R.style.m3_body_medium); + text.setText(switch(page){ + case 0 -> R.string.welcome_page1_text; + case 1 -> R.string.welcome_page2_text; + case 2 -> R.string.welcome_page3_text; + default -> throw new IllegalStateException("Unexpected value: "+page); + }); + text.setTextColor(0xFF17063B); + ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + } + } + + private class LogoSpan extends ReplacementSpan{ + private final Drawable drawable; + + private LogoSpan(Drawable drawable){ + this.drawable=drawable; + } + + @Override + public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){ + return drawable.getIntrinsicWidth(); + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint){ + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + canvas.save(); + canvas.translate(x, y-V.dp(20)); + drawable.draw(canvas); + canvas.restore(); + } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/utils/MastodonLanguage.java b/mastodon/src/main/java/org/joinmastodon/android/utils/MastodonLanguage.java new file mode 100644 index 000000000..2e4eb949a --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/utils/MastodonLanguage.java @@ -0,0 +1,101 @@ + +package org.joinmastodon.android.utils; + +import static org.joinmastodon.android.api.MastodonAPIController.gson; + +import android.content.res.Resources; +import android.os.Build; +import android.os.LocaleList; + +import org.joinmastodon.android.model.Instance; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; + +public class MastodonLanguage { + // On an up-to-date Mastodon instance: + // copy(JSON.stringify(JSON.stringify(JSON.parse(document.getElementById('initial-state').textContent).languages))) + public static String languagesJson = + "[[\"aa\",\"Afar\",\"Afaraf\"],[\"ab\",\"Abkhaz\",\"аҧсуа бызшәа\"],[\"ae\",\"Avestan\",\"avesta\"],[\"af\",\"Afrikaans\",\"Afrikaans\"],[\"ak\",\"Akan\",\"Akan\"],[\"am\",\"Amharic\",\"አማርኛ\"],[\"an\",\"Aragonese\",\"aragonés\"],[\"ar\",\"Arabic\",\"اللغة العربية\"],[\"as\",\"Assamese\",\"অসমীয়া\"],[\"av\",\"Avaric\",\"авар мацӀ\"],[\"ay\",\"Aymara\",\"aymar aru\"],[\"az\",\"Azerbaijani\",\"azərbaycan dili\"],[\"ba\",\"Bashkir\",\"башҡорт теле\"],[\"be\",\"Belarusian\",\"беларуская мова\"],[\"bg\",\"Bulgarian\",\"български език\"],[\"bh\",\"Bihari\",\"भोजपुरी\"],[\"bi\",\"Bislama\",\"Bislama\"],[\"bm\",\"Bambara\",\"bamanankan\"],[\"bn\",\"Bengali\",\"বাংলা\"],[\"bo\",\"Tibetan\",\"བོད་ཡིག\"],[\"br\",\"Breton\",\"brezhoneg\"],[\"bs\",\"Bosnian\",\"bosanski jezik\"],[\"ca\",\"Catalan\",\"Català\"],[\"ce\",\"Chechen\",\"нохчийн мотт\"],[\"ch\",\"Chamorro\",\"Chamoru\"],[\"co\",\"Corsican\",\"corsu\"],[\"cr\",\"Cree\",\"ᓀᐦᐃᔭᐍᐏᐣ\"],[\"cs\",\"Czech\",\"čeština\"],[\"cu\",\"Old Church Slavonic\",\"ѩзыкъ словѣньскъ\"],[\"cv\",\"Chuvash\",\"чӑваш чӗлхи\"],[\"cy\",\"Welsh\",\"Cymraeg\"],[\"da\",\"Danish\",\"dansk\"],[\"de\",\"German\",\"Deutsch\"],[\"dv\",\"Divehi\",\"Dhivehi\"],[\"dz\",\"Dzongkha\",\"རྫོང་ཁ\"],[\"ee\",\"Ewe\",\"Eʋegbe\"],[\"el\",\"Greek\",\"Ελληνικά\"],[\"en\",\"English\",\"English\"],[\"eo\",\"Esperanto\",\"Esperanto\"],[\"es\",\"Spanish\",\"Español\"],[\"et\",\"Estonian\",\"eesti\"],[\"eu\",\"Basque\",\"euskara\"],[\"fa\",\"Persian\",\"فارسی\"],[\"ff\",\"Fula\",\"Fulfulde\"],[\"fi\",\"Finnish\",\"suomi\"],[\"fj\",\"Fijian\",\"Vakaviti\"],[\"fo\",\"Faroese\",\"føroyskt\"],[\"fr\",\"French\",\"Français\"],[\"fy\",\"Western Frisian\",\"Frysk\"],[\"ga\",\"Irish\",\"Gaeilge\"],[\"gd\",\"Scottish Gaelic\",\"Gàidhlig\"],[\"gl\",\"Galician\",\"galego\"],[\"gu\",\"Gujarati\",\"ગુજરાતી\"],[\"gv\",\"Manx\",\"Gaelg\"],[\"ha\",\"Hausa\",\"هَوُسَ\"],[\"he\",\"Hebrew\",\"עברית\"],[\"hi\",\"Hindi\",\"हिन्दी\"],[\"ho\",\"Hiri Motu\",\"Hiri Motu\"],[\"hr\",\"Croatian\",\"Hrvatski\"],[\"ht\",\"Haitian\",\"Kreyòl ayisyen\"],[\"hu\",\"Hungarian\",\"magyar\"],[\"hy\",\"Armenian\",\"Հայերեն\"],[\"hz\",\"Herero\",\"Otjiherero\"],[\"ia\",\"Interlingua\",\"Interlingua\"],[\"id\",\"Indonesian\",\"Bahasa Indonesia\"],[\"ie\",\"Interlingue\",\"Interlingue\"],[\"ig\",\"Igbo\",\"Asụsụ Igbo\"],[\"ii\",\"Nuosu\",\"ꆈꌠ꒿ Nuosuhxop\"],[\"ik\",\"Inupiaq\",\"Iñupiaq\"],[\"io\",\"Ido\",\"Ido\"],[\"is\",\"Icelandic\",\"Íslenska\"],[\"it\",\"Italian\",\"Italiano\"],[\"iu\",\"Inuktitut\",\"ᐃᓄᒃᑎᑐᑦ\"],[\"ja\",\"Japanese\",\"日本語\"],[\"jv\",\"Javanese\",\"basa Jawa\"],[\"ka\",\"Georgian\",\"ქართული\"],[\"kg\",\"Kongo\",\"Kikongo\"],[\"ki\",\"Kikuyu\",\"Gĩkũyũ\"],[\"kj\",\"Kwanyama\",\"Kuanyama\"],[\"kk\",\"Kazakh\",\"қазақ тілі\"],[\"kl\",\"Kalaallisut\",\"kalaallisut\"],[\"km\",\"Khmer\",\"ខេមរភាសា\"],[\"kn\",\"Kannada\",\"ಕನ್ನಡ\"],[\"ko\",\"Korean\",\"한국어\"],[\"kr\",\"Kanuri\",\"Kanuri\"],[\"ks\",\"Kashmiri\",\"कश्मीरी\"],[\"ku\",\"Kurdish\",\"Kurdî\"],[\"kv\",\"Komi\",\"коми кыв\"],[\"kw\",\"Cornish\",\"Kernewek\"],[\"ky\",\"Kyrgyz\",\"Кыргызча\"],[\"la\",\"Latin\",\"latine\"],[\"lb\",\"Luxembourgish\",\"Lëtzebuergesch\"],[\"lg\",\"Ganda\",\"Luganda\"],[\"li\",\"Limburgish\",\"Limburgs\"],[\"ln\",\"Lingala\",\"Lingála\"],[\"lo\",\"Lao\",\"ລາວ\"],[\"lt\",\"Lithuanian\",\"lietuvių kalba\"],[\"lu\",\"Luba-Katanga\",\"Tshiluba\"],[\"lv\",\"Latvian\",\"latviešu valoda\"],[\"mg\",\"Malagasy\",\"fiteny malagasy\"],[\"mh\",\"Marshallese\",\"Kajin M̧ajeļ\"],[\"mi\",\"Māori\",\"te reo Māori\"],[\"mk\",\"Macedonian\",\"македонски јазик\"],[\"ml\",\"Malayalam\",\"മലയാളം\"],[\"mn\",\"Mongolian\",\"Монгол хэл\"],[\"mr\",\"Marathi\",\"मराठी\"],[\"ms\",\"Malay\",\"Bahasa Melayu\"],[\"mt\",\"Maltese\",\"Malti\"],[\"my\",\"Burmese\",\"ဗမာစာ\"],[\"na\",\"Nauru\",\"Ekakairũ Naoero\"],[\"nb\",\"Norwegian Bokmål\",\"Norsk bokmål\"],[\"nd\",\"Northern Ndebele\",\"isiNdebele\"],[\"ne\",\"Nepali\",\"नेपाली\"],[\"ng\",\"Ndonga\",\"Owambo\"],[\"nl\",\"Dutch\",\"Nederlands\"],[\"nn\",\"Norwegian Nynorsk\",\"Norsk Nynorsk\"],[\"no\",\"Norwegian\",\"Norsk\"],[\"nr\",\"Southern Ndebele\",\"isiNdebele\"],[\"nv\",\"Navajo\",\"Diné bizaad\"],[\"ny\",\"Chichewa\",\"chiCheŵa\"],[\"oc\",\"Occitan\",\"occitan\"],[\"oj\",\"Ojibwe\",\"ᐊᓂᔑᓈᐯᒧᐎᓐ\"],[\"om\",\"Oromo\",\"Afaan Oromoo\"],[\"or\",\"Oriya\",\"ଓଡ଼ିଆ\"],[\"os\",\"Ossetian\",\"ирон æвзаг\"],[\"pa\",\"Panjabi\",\"ਪੰਜਾਬੀ\"],[\"pi\",\"Pāli\",\"पाऴि\"],[\"pl\",\"Polish\",\"Polski\"],[\"ps\",\"Pashto\",\"پښتو\"],[\"pt\",\"Portuguese\",\"Português\"],[\"qu\",\"Quechua\",\"Runa Simi\"],[\"rm\",\"Romansh\",\"rumantsch grischun\"],[\"rn\",\"Kirundi\",\"Ikirundi\"],[\"ro\",\"Romanian\",\"Română\"],[\"ru\",\"Russian\",\"Русский\"],[\"rw\",\"Kinyarwanda\",\"Ikinyarwanda\"],[\"sa\",\"Sanskrit\",\"संस्कृतम्\"],[\"sc\",\"Sardinian\",\"sardu\"],[\"sd\",\"Sindhi\",\"सिन्धी\"],[\"se\",\"Northern Sami\",\"Davvisámegiella\"],[\"sg\",\"Sango\",\"yângâ tî sängö\"],[\"si\",\"Sinhala\",\"සිංහල\"],[\"sk\",\"Slovak\",\"slovenčina\"],[\"sl\",\"Slovenian\",\"slovenščina\"],[\"sn\",\"Shona\",\"chiShona\"],[\"so\",\"Somali\",\"Soomaaliga\"],[\"sq\",\"Albanian\",\"Shqip\"],[\"sr\",\"Serbian\",\"српски језик\"],[\"ss\",\"Swati\",\"SiSwati\"],[\"st\",\"Southern Sotho\",\"Sesotho\"],[\"su\",\"Sundanese\",\"Basa Sunda\"],[\"sv\",\"Swedish\",\"Svenska\"],[\"sw\",\"Swahili\",\"Kiswahili\"],[\"ta\",\"Tamil\",\"தமிழ்\"],[\"te\",\"Telugu\",\"తెలుగు\"],[\"tg\",\"Tajik\",\"тоҷикӣ\"],[\"th\",\"Thai\",\"ไทย\"],[\"ti\",\"Tigrinya\",\"ትግርኛ\"],[\"tk\",\"Turkmen\",\"Türkmen\"],[\"tl\",\"Tagalog\",\"Wikang Tagalog\"],[\"tn\",\"Tswana\",\"Setswana\"],[\"to\",\"Tonga\",\"faka Tonga\"],[\"tr\",\"Turkish\",\"Türkçe\"],[\"ts\",\"Tsonga\",\"Xitsonga\"],[\"tt\",\"Tatar\",\"татар теле\"],[\"tw\",\"Twi\",\"Twi\"],[\"ty\",\"Tahitian\",\"Reo Tahiti\"],[\"ug\",\"Uyghur\",\"ئۇيغۇرچە‎\"],[\"uk\",\"Ukrainian\",\"Українська\"],[\"ur\",\"Urdu\",\"اردو\"],[\"uz\",\"Uzbek\",\"Ўзбек\"],[\"ve\",\"Venda\",\"Tshivenḓa\"],[\"vi\",\"Vietnamese\",\"Tiếng Việt\"],[\"vo\",\"Volapük\",\"Volapük\"],[\"wa\",\"Walloon\",\"walon\"],[\"wo\",\"Wolof\",\"Wollof\"],[\"xh\",\"Xhosa\",\"isiXhosa\"],[\"yi\",\"Yiddish\",\"ייִדיש\"],[\"yo\",\"Yoruba\",\"Yorùbá\"],[\"za\",\"Zhuang\",\"Saɯ cueŋƅ\"],[\"zh\",\"Chinese\",\"中文\"],[\"zu\",\"Zulu\",\"isiZulu\"],[\"ast\",\"Asturian\",\"Asturianu\"],[\"ckb\",\"Sorani (Kurdish)\",\"سۆرانی\"],[\"jbo\",\"Lojban\",\"la .lojban.\"],[\"kab\",\"Kabyle\",\"Taqbaylit\"],[\"kmr\",\"Kurmanji (Kurdish)\",\"Kurmancî\"],[\"ldn\",\"Láadan\",\"Láadan\"],[\"lfn\",\"Lingua Franca Nova\",\"lingua franca nova\"],[\"sco\",\"Scots\",\"Scots\"],[\"tok\",\"Toki Pona\",\"toki pona\"],[\"zba\",\"Balaibalan\",\"باليبلن\"],[\"zgh\",\"Standard Moroccan Tamazight\",\"ⵜⴰⵎⴰⵣⵉⵖⵜ\"]]"; + public static final List allLanguages; + public static final MastodonLanguage ENGLISH = new MastodonLanguage("en", "English", "English"); + + static { + String[][] languages = gson.fromJson(languagesJson, String[][].class); + allLanguages = new ArrayList<>(); + for (String[] language : languages) allLanguages.add(new MastodonLanguage(language[0], language[1], language[2])); + Collections.sort(allLanguages, Comparator.comparing(MastodonLanguage::getDefaultName)); + } + + public static List 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.toList()); + } + + public final String languageTag, name, englishName; + public final Locale locale; + + private MastodonLanguage(String languageTag, String englishName, String name) { + this.locale = new Locale(languageTag); + this.languageTag = languageTag.toLowerCase(Locale.ROOT); + this.name = name; + this.englishName = englishName; + } + + public String getDefaultName() { + String accordingToLocale = locale.getDisplayLanguage(Locale.getDefault()); + return accordingToLocale.equals(languageTag) ? englishName : accordingToLocale; + } + + public String getLanguageName() { return name; } + + public String getLanguage() { return languageTag; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + return Objects.equals(languageTag, ((MastodonLanguage) o).languageTag); + } + + @Override + public int hashCode() { + return languageTag != null ? languageTag.hashCode() : 0; + } + + public static class LanguageResolver { + private final MastodonLanguage fallbackLanguage; + + public LanguageResolver(Instance instanceInfo) { + String fallbackLanguageTag = !instanceInfo.languages.isEmpty() ? instanceInfo.languages.get(0) : ENGLISH.languageTag; + fallbackLanguage = allLanguages.stream() + .filter(l->l.languageTag.equalsIgnoreCase(fallbackLanguageTag)).findAny() + .orElse(ENGLISH); + } + + public MastodonLanguage from(String language) { + return allLanguages.stream() + .filter(l->l.locale.equals(new Locale(language))).findAny() + .orElse(fallbackLanguage); + } + + public MastodonLanguage getDefault() { + return from(Locale.getDefault().getLanguage()); + } + } +} + diff --git a/mastodon/src/main/res/color/button_text_m3_text.xml b/mastodon/src/main/res/color/button_text_m3_text.xml new file mode 100644 index 000000000..a2bb33cf2 --- /dev/null +++ b/mastodon/src/main/res/color/button_text_m3_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/color/m3_primary_overlay.xml b/mastodon/src/main/res/color/m3_primary_overlay.xml new file mode 100644 index 000000000..38153edf5 --- /dev/null +++ b/mastodon/src/main/res/color/m3_primary_overlay.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/bg_button_m3_text.xml b/mastodon/src/main/res/drawable/bg_button_m3_text.xml new file mode 100644 index 000000000..a49879ef7 --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_button_m3_text.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/drawable/splash_logo.xml b/mastodon/src/main/res/drawable/splash_logo.xml index cb724a171..0486618e4 100644 --- a/mastodon/src/main/res/drawable/splash_logo.xml +++ b/mastodon/src/main/res/drawable/splash_logo.xml @@ -1,7 +1,7 @@ + android:fillColor="#000"/> + android:fillColor="#000"/> diff --git a/mastodon/src/main/res/drawable/white_circle.xml b/mastodon/src/main/res/drawable/white_circle.xml new file mode 100644 index 000000000..72bfc3def --- /dev/null +++ b/mastodon/src/main/res/drawable/white_circle.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/layout/fragment_splash.xml b/mastodon/src/main/res/layout/fragment_splash.xml index afabb10a1..23e7cc657 100644 --- a/mastodon/src/main/res/layout/fragment_splash.xml +++ b/mastodon/src/main/res/layout/fragment_splash.xml @@ -1,12 +1,11 @@ - + android:clipToPadding="false"> + android:layout_width="360dp" + android:layout_height="640dp" + android:layout_gravity="center" + tools:ignore="rtlHardcoded"> - + + + - - - - -