Quotes in text formatting (AND-222)
This commit is contained in:
parent
82c6c8076a
commit
77b2f98f17
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue