Quotes in text formatting (AND-222)

This commit is contained in:
Grishka 2024-10-09 05:20:58 +03:00
parent 82c6c8076a
commit 77b2f98f17
5 changed files with 110 additions and 8 deletions

View File

@ -0,0 +1,70 @@
package org.joinmastodon.android.ui.text;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.CharacterStyle;
import android.text.style.LeadingMarginSpan;
import org.joinmastodon.android.R;
import org.joinmastodon.android.ui.utils.UiUtils;
import androidx.annotation.NonNull;
import me.grishka.appkit.utils.V;
public class BlockQuoteSpan extends CharacterStyle implements LeadingMarginSpan{
private final Context context;
private Drawable icon;
private boolean firstLevel;
private Paint paint=new Paint();
public BlockQuoteSpan(Context context, boolean firstLevel){
this.context=context;
icon=context.getResources().getDrawable(R.drawable.quote, context.getTheme());
this.firstLevel=firstLevel;
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorM3TertiaryContainer));
paint.setAlpha(51);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(V.dp(3));
}
@Override
public int getLeadingMargin(boolean first){
return V.dp(firstLevel ? 32 : 18);
}
@Override
public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, int top, int baseline, int bottom, @NonNull CharSequence text, int start, int end, boolean first, @NonNull Layout layout){
if(text instanceof Spanned s && s.getSpanStart(this)==start){
int level=s.getSpans(start, end, LeadingMarginSpan.class).length-1;
if(dir<0){ // RTL
// c.drawText(this.text, layout.getWidth()-V.dp(32*level)-p.measureText(this.text), baseline, p);
if(level==0){
icon.setBounds(layout.getWidth()-icon.getIntrinsicWidth(), top, layout.getWidth(), top+icon.getIntrinsicHeight());
icon.draw(c);
}else{
float xOffset=layout.getWidth()-V.dp(32+18*(level-1)+1.5f);
c.drawLine(xOffset, top, xOffset, layout.getLineBottom(layout.getLineForOffset(s.getSpanEnd(this))), paint);
}
}else{
if(level==0){
icon.setBounds(x, top, x+icon.getIntrinsicWidth(), top+icon.getIntrinsicHeight());
icon.draw(c);
}else{
float xOffset=x+V.dp(32+18*(level-1)+1.5f);
c.drawLine(xOffset, top, xOffset, layout.getLineBottom(layout.getLineForOffset(s.getSpanEnd(this))), paint);
}
}
}
}
@Override
public void updateDrawState(TextPaint tp){
tp.setColor(UiUtils.getThemeColor(context, R.attr.colorM3Tertiary));
}
}

View File

@ -8,6 +8,8 @@ import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LineHeightSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.widget.TextView;
@ -105,6 +107,14 @@ public class HtmlParser{
return false;
}
private boolean isInsideBlockquote(){
for(SpanInfo si:openSpans){
if(si.span instanceof BlockQuoteSpan)
return true;
}
return false;
}
@SuppressLint("DefaultLocale")
@Override
public void head(@NonNull Node node, int depth){
@ -158,7 +168,7 @@ public class HtmlParser{
case "code" -> {
if(!isInsidePre()){
openSpans.add(new SpanInfo(new MonospaceSpan(context), ssb.length(), el));
ssb.append(" ", new SpacerSpan(V.dp(4), 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(" ", new SpacerSpan(V.dp(4), 0), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
case "pre" -> openSpans.add(new SpanInfo(new CodeBlockSpan(context), ssb.length(), el));
@ -185,6 +195,11 @@ public class HtmlParser{
copyableText.append(' ');
ssb.append(copyableText.toString(), new InvisibleSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
case "blockquote" -> {
if(ssb.length()>0 && ssb.charAt(ssb.length()-1)!='\n')
ssb.append('\n');
openSpans.add(new SpanInfo(new BlockQuoteSpan(context, !isInsideBlockquote()), ssb.length(), el));
}
}
}
}
@ -195,9 +210,11 @@ public class HtmlParser{
String name=el.nodeName();
if("span".equals(name) && el.hasClass("ellipsis")){
ssb.append("", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}else if("p".equals(name) || ("ol".equals(name) || "ul".equals(name))){
if(node.nextSibling()!=null)
ssb.append("\n\n");
}else if("p".equals(name) || "ol".equals(name) || "ul".equals(name)){
if(node.nextSibling()!=null && "body".equals(node.parent().nodeName())){
ssb.append('\n');
ssb.append("\n", new SpacerSpan(1, V.dp(8)), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}else if("pre".equals(name)){
if(node.nextSibling()!=null)
ssb.append("\n");
@ -207,7 +224,7 @@ public class HtmlParser{
if(si.element==el){
if(si.span!=null){
if(si.span instanceof MonospaceSpan){
ssb.append(" ", new SpacerSpan(V.dp(4), 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ssb.append(" ", new SpacerSpan(V.dp(4), 0), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
ssb.setSpan(si.span, si.start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}

View File

@ -17,7 +17,12 @@ public class SpacerSpan extends ReplacementSpan{
@Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm){
// TODO height
if(fm!=null && height>0){
fm.ascent=-height;
fm.descent=0;
fm.top=fm.ascent;
fm.bottom=0;
}
return width;
}

View File

@ -713,8 +713,8 @@ public class UiUtils{
item.setIcon(icon);
SpannableStringBuilder ssb=new SpannableStringBuilder(item.getTitle());
ssb.insert(0, " ");
ssb.setSpan(new SpacerSpan(V.dp(24), 1), 0, 1, 0);
ssb.append(" ", new SpacerSpan(V.dp(8), 1), 0);
ssb.setSpan(new SpacerSpan(V.dp(24), 0), 0, 1, 0);
ssb.append(" ", new SpacerSpan(V.dp(8), 0), 0);
item.setTitle(ssb);
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="20">
<path
android:fillColor="?colorM3TertiaryContainer"
android:fillAlpha="0.2"
android:pathData="M23.933,2.824C22.324,4.079 21.073,5.357 20.179,6.657C19.33,7.912 18.905,9.1 18.905,10.221C19.084,10.131 19.307,10.086 19.575,10.086C19.888,10.041 20.156,10.019 20.38,10.019C21.408,10.019 22.257,10.445 22.927,11.297C23.642,12.103 24,13.112 24,14.322C24,15.802 23.508,17.035 22.525,18.021C21.542,19.007 20.313,19.5 18.838,19.5C17.274,19.5 16.045,18.94 15.151,17.819C14.257,16.653 13.81,15.107 13.81,13.179C13.81,10.893 14.503,8.629 15.888,6.388C17.274,4.147 19.307,2.017 21.989,0L23.933,2.824ZM10.123,2.824C8.514,4.079 7.263,5.357 6.369,6.657C5.52,7.912 5.095,9.1 5.095,10.221C5.274,10.131 5.497,10.086 5.765,10.086C6.078,10.041 6.346,10.019 6.57,10.019C7.598,10.019 8.447,10.445 9.117,11.297C9.832,12.103 10.19,13.112 10.19,14.322C10.19,15.802 9.698,17.035 8.715,18.021C7.732,19.007 6.503,19.5 5.028,19.5C3.464,19.5 2.235,18.94 1.341,17.819C0.447,16.653 0,15.107 0,13.179C0,10.893 0.693,8.629 2.078,6.388C3.464,4.147 5.497,2.017 8.179,0L10.123,2.824Z"/>
</vector>