Merge branch 'main' into fork

# Conflicts:
#	README.md
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/color/button_text_primary_light_on_dark.xml
#	mastodon/src/main/res/drawable/logo.xml
#	mastodon/src/main/res/layout/item_settings_color_picker.xml
#	mastodon/src/main/res/menu/color_picker.xml
#	mastodon/src/main/res/values-night/styles.xml
#	mastodon/src/main/res/values-v27/colors.xml
#	mastodon/src/main/res/values/colors.xml
#	mastodon/src/main/res/values/strings.xml
#	mastodon/src/main/res/values/styles.xml
This commit is contained in:
LucasGGamerM
2022-12-09 19:08:35 -03:00
189 changed files with 3412 additions and 902 deletions

View File

@@ -28,6 +28,7 @@ import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -154,11 +155,16 @@ public class ComposeAutocompleteViewController{
}else if(mode==Mode.EMOJIS){
String _text=text.substring(1); // remove ':'
List<WrappedEmoji> oldList=emojis;
emojis=AccountSessionManager.getInstance()
List<Emoji> allEmojis = AccountSessionManager.getInstance()
.getCustomEmojis(AccountSessionManager.getInstance().getAccount(accountID).domain)
.stream()
.flatMap(ec->ec.emojis.stream())
.filter(e->e.visibleInPicker && e.shortcode.startsWith(_text))
.filter(e->e.visibleInPicker)
.collect(Collectors.toList());
List<Emoji> startsWithSearch = allEmojis.stream().filter(e -> e.shortcode.toLowerCase().startsWith(_text.toLowerCase())).collect(Collectors.toList());
emojis=Stream.concat(startsWithSearch.stream(), allEmojis.stream()
.filter(e -> !startsWithSearch.contains(e))
.filter(e -> e.shortcode.toLowerCase().contains(_text.toLowerCase())))
.map(WrappedEmoji::new)
.collect(Collectors.toList());
UiUtils.updateList(oldList, emojis, list, emojisAdapter, (e1, e2)->e1.emoji.shortcode.equals(e2.emoji.shortcode));

View File

@@ -45,7 +45,7 @@ public class ImageDescriptionSheet extends BottomSheet{
}
TextView heading=new TextView(activity);
heading.setText(R.string.image_description);
heading.setText(R.string.sk_image_description);
heading.setAllCaps(true);
heading.setTypeface(null, Typeface.BOLD);
heading.setPadding(0, V.dp(24), 0, V.dp(8));

View File

@@ -147,7 +147,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
if(id==R.id.favorite_btn)
return R.string.button_favorite;
if(id==R.id.bookmark_btn)
return R.string.button_bookmark;
return R.string.add_bookmark;
if(id==R.id.share_btn)
return R.string.button_share;
return 0;

View File

@@ -6,6 +6,7 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Poll;
@@ -44,7 +45,7 @@ public class PollFooterStatusDisplayItem extends StatusDisplayItem{
text+=" · "+item.parentFragment.getString(R.string.poll_closed);
}
this.text.setText(text);
button.setVisibility(item.poll.isExpired() || item.poll.voted || !item.poll.multiple ? View.GONE : View.VISIBLE);
button.setVisibility(item.poll.isExpired() || item.poll.voted || (!item.poll.multiple && !GlobalUserPreferences.voteButtonForSingleChoice) ? View.GONE : View.VISIBLE);
button.setEnabled(item.poll.selectedOptions!=null && !item.poll.selectedOptions.isEmpty());
}
}

View File

@@ -76,10 +76,11 @@ public class PollOptionStatusDisplayItem extends StatusDisplayItem{
@Override
public void onBind(PollOptionStatusDisplayItem item){
text.setText(item.text);
icon.setVisibility(item.showResults ? View.GONE : View.VISIBLE);
// icon.setVisibility(item.showResults ? View.GONE : View.VISIBLE);
percent.setVisibility(item.showResults ? View.VISIBLE : View.GONE);
itemView.setClickable(!item.showResults);
if(item.showResults){
icon.setSelected(item.poll.ownVotes.contains(item.poll.options.indexOf(item.option)));
progressBg.setLevel(Math.round(10000f*item.votesFraction));
button.setBackground(progressBg);
itemView.setSelected(item.isMostVoted);

View File

@@ -1,9 +1,12 @@
package org.joinmastodon.android.ui.displayitems;
import static org.joinmastodon.android.MastodonApp.context;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -38,6 +41,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
emojiHelper.setText(ssb);
this.icon=icon;
this.handleClick=handleClick;
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
}
@Override
@@ -67,6 +72,8 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0);
if(item.handleClick!=null) text.setOnClickListener(item.handleClick);
text.setEnabled(!item.inset);
text.setClickable(!item.inset);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
}

View File

@@ -178,7 +178,7 @@ public class PhotoViewer implements ZoomPanView.Listener{
toolbar=uiOverlay.findViewById(R.id.toolbar);
toolbar.setNavigationOnClickListener(v->onStartSwipeToDismissTransition(0));
imageDescriptionButton = toolbar.getMenu()
.add(R.string.image_description)
.add(R.string.sk_image_description)
.setIcon(R.drawable.ic_fluent_image_alt_text_24_regular)
.setVisible(attachments.get(pager.getCurrentItem()).description != null
&& !attachments.get(pager.getCurrentItem()).description.isEmpty())

View File

@@ -1,12 +1,29 @@
package org.joinmastodon.android.ui.text;
import android.graphics.Typeface;
import android.graphics.fonts.FontFamily;
import android.graphics.fonts.FontStyle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.BulletSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.SubscriptSpan;
import android.text.style.SuperscriptSpan;
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
import android.util.TypedValue;
import android.widget.TextView;
import com.twitter.twittertext.Regex;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Mention;
@@ -15,11 +32,11 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Safelist;
import org.jsoup.select.NodeVisitor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -29,6 +46,8 @@ import java.util.stream.Collectors;
import androidx.annotation.NonNull;
import me.grishka.appkit.utils.V;
public class HtmlParser{
private static final String TAG="HtmlParser";
private static final String VALID_URL_PATTERN_STRING =
@@ -67,11 +86,17 @@ public class HtmlParser{
public Object span;
public int start;
public Element element;
public boolean more;
public SpanInfo(Object span, int start, Element element){
this(span, start, element, false);
}
public SpanInfo(Object span, int start, Element element, boolean more){
this.span=span;
this.start=start;
this.element=element;
this.more=more;
}
}
@@ -119,24 +144,59 @@ public class HtmlParser{
openSpans.add(new SpanInfo(new InvisibleSpan(), ssb.length(), el));
}
}
case "li" -> openSpans.add(new SpanInfo(new BulletSpan(V.dp(8)), ssb.length(), el));
case "em", "i" -> openSpans.add(new SpanInfo(new StyleSpan(Typeface.ITALIC), ssb.length(), el));
case "h1", "h2", "h3", "h4", "h5", "h6" -> {
// increase line height above heading (multiplying the margin)
if (node.previousSibling()!=null) ssb.setSpan(new RelativeSizeSpan(2), ssb.length() - 1, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (!node.nodeName().equals("h1")) {
openSpans.add(new SpanInfo(new StyleSpan(Typeface.BOLD), ssb.length(), el));
}
openSpans.add(new SpanInfo(new RelativeSizeSpan(switch(node.nodeName()) {
case "h1" -> 1.5f;
case "h2" -> 1.25f;
case "h3" -> 1.125f;
default -> 1;
}), ssb.length(), el, !node.nodeName().equals("h1")));
}
case "strong", "b" -> openSpans.add(new SpanInfo(new StyleSpan(Typeface.BOLD), ssb.length(), el));
case "u" -> openSpans.add(new SpanInfo(new UnderlineSpan(), ssb.length(), el));
case "s", "del" -> openSpans.add(new SpanInfo(new StrikethroughSpan(), ssb.length(), el));
case "sub", "sup" -> {
openSpans.add(new SpanInfo(node.nodeName().equals("sub") ? new SubscriptSpan() : new SuperscriptSpan(), ssb.length(), el));
openSpans.add(new SpanInfo(new RelativeSizeSpan(0.8f), ssb.length(), el, true));
}
case "code", "pre" -> openSpans.add(new SpanInfo(new TypefaceSpan("monospace"), ssb.length(), el));
case "blockquote" -> openSpans.add(new SpanInfo(new LeadingMarginSpan.Standard(V.dp(10)), ssb.length(), el));
}
}
}
final static List<String> blockElements = Arrays.asList("p", "ul", "ol", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6");
@Override
public void tail(@NonNull Node node, int depth){
if(node instanceof Element el){
processOpenSpan(el);
if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){
ssb.append("", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}else if("p".equals(el.nodeName())){
if(node.nextSibling()!=null)
ssb.append("\n\n");
}else if(!openSpans.isEmpty()){
SpanInfo si=openSpans.get(openSpans.size()-1);
if(si.element==el){
ssb.setSpan(si.span, si.start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
openSpans.remove(openSpans.size()-1);
}
}else if(blockElements.contains(el.nodeName()) && node.nextSibling()!=null){
ssb.append("\n"); // line end
ssb.append("\n", new RelativeSizeSpan(0.75f), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // margin after block
}
}
}
private void processOpenSpan(Element el) {
if(!openSpans.isEmpty()){
SpanInfo si=openSpans.get(openSpans.size()-1);
if(si.element==el){
ssb.setSpan(si.span, si.start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
openSpans.remove(openSpans.size()-1);
if(si.more) processOpenSpan(el);
}
if("li".equals(el.nodeName()) && el.nextSibling()!=null) {
ssb.append('\n');
}
}
}

View File

@@ -36,7 +36,7 @@ public class DiscoverInfoBannerHelper{
case TRENDING_HASHTAGS -> R.string.trending_hashtags_info_banner;
case TRENDING_LINKS -> R.string.trending_links_info_banner;
case LOCAL_TIMELINE -> R.string.local_timeline_info_banner;
case FEDERATED_TIMELINE -> R.string.federated_timeline_info_banner;
case FEDERATED_TIMELINE -> R.string.sk_federated_timeline_info_banner;
});
}
}

View File

@@ -0,0 +1,124 @@
package org.joinmastodon.android.ui.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Editable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.SimpleTextWatcher;
import me.grishka.appkit.utils.CubicBezierInterpolator;
import me.grishka.appkit.utils.V;
public class FloatingHintEditTextLayout extends FrameLayout{
private EditText edit;
private TextView label;
private int labelTextSize;
private int offsetY;
private boolean hintVisible;
private Animator currentAnim;
public FloatingHintEditTextLayout(Context context){
this(context, null);
}
public FloatingHintEditTextLayout(Context context, AttributeSet attrs){
this(context, attrs, 0);
}
public FloatingHintEditTextLayout(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
if(isInEditMode())
V.setApplicationContext(context);
TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.FloatingHintEditTextLayout);
labelTextSize=ta.getDimensionPixelSize(R.styleable.FloatingHintEditTextLayout_android_labelTextSize, V.dp(12));
offsetY=ta.getDimensionPixelOffset(R.styleable.FloatingHintEditTextLayout_editTextOffsetY, 0);
ta.recycle();
}
@Override
protected void onFinishInflate(){
super.onFinishInflate();
if(getChildCount()>0 && getChildAt(0) instanceof EditText et){
edit=et;
}else{
throw new IllegalStateException("First child must be an EditText");
}
label=new TextView(getContext());
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, labelTextSize);
label.setTextColor(edit.getHintTextColors());
label.setText(edit.getHint());
label.setSingleLine();
label.setPivotX(0f);
label.setPivotY(0f);
LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP);
lp.setMarginStart(edit.getPaddingStart());
addView(label, lp);
hintVisible=edit.getText().length()==0;
if(hintVisible)
label.setAlpha(0f);
edit.addTextChangedListener(new SimpleTextWatcher(this::onTextChanged));
}
private void onTextChanged(Editable text){
boolean newHintVisible=text.length()==0;
if(newHintVisible==hintVisible)
return;
if(currentAnim!=null)
currentAnim.cancel();
hintVisible=newHintVisible;
label.setAlpha(1);
float scale=edit.getLineHeight()/(float)label.getLineHeight();
float transY=edit.getHeight()/2f-edit.getLineHeight()/2f+(edit.getTop()-label.getTop())-(label.getHeight()/2f-label.getLineHeight()/2f);
AnimatorSet anim=new AnimatorSet();
if(hintVisible){
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, 0),
ObjectAnimator.ofFloat(label, SCALE_X, scale),
ObjectAnimator.ofFloat(label, SCALE_Y, scale),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, transY)
);
edit.setHintTextColor(0);
}else{
label.setScaleX(scale);
label.setScaleY(scale);
label.setTranslationY(transY);
anim.playTogether(
ObjectAnimator.ofFloat(edit, TRANSLATION_Y, offsetY),
ObjectAnimator.ofFloat(label, SCALE_X, 1f),
ObjectAnimator.ofFloat(label, SCALE_Y, 1f),
ObjectAnimator.ofFloat(label, TRANSLATION_Y, 0f)
);
}
anim.setDuration(150);
anim.setInterpolator(CubicBezierInterpolator.DEFAULT);
anim.start();
anim.addListener(new AnimatorListenerAdapter(){
@Override
public void onAnimationEnd(Animator animation){
currentAnim=null;
if(hintVisible){
label.setAlpha(0);
edit.setHintTextColor(label.getTextColors());
}
}
});
currentAnim=anim;
}
}