mirror of
https://github.com/mastodon/mastodon-android.git
synced 2024-12-22 14:44:21 +01:00
Monospace text formatting (AND-223)
This commit is contained in:
parent
513a57663b
commit
4177faa553
@ -90,7 +90,7 @@ dependencies {
|
||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||
implementation 'me.grishka.litex:palette:1.0.0'
|
||||
implementation 'me.grishka.appkit:appkit:1.4.2'
|
||||
implementation 'me.grishka.appkit:appkit:1.4.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.jsoup:jsoup:1.14.3'
|
||||
implementation 'com.squareup:otto:1.3.8'
|
||||
|
@ -84,7 +84,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
for(Account acc:result)
|
||||
accountIDsInList.add(acc.id);
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()));
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID, getActivity())).collect(Collectors.toList()));
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
@ -190,7 +190,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
@Subscribe
|
||||
public void onAccountAddedToList(AccountAddedToListEvent ev){
|
||||
if(ev.accountID.equals(accountID) && ev.listID.equals(followList.id)){
|
||||
data.add(new AccountViewModel(ev.account, accountID));
|
||||
data.add(new AccountViewModel(ev.account, accountID, getActivity()));
|
||||
list.getAdapter().notifyItemInserted(data.size()-1);
|
||||
}
|
||||
}
|
||||
@ -337,7 +337,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
||||
onDone.run();
|
||||
for(Account acc:accounts){
|
||||
accountIDsInList.add(acc.id);
|
||||
data.add(new AccountViewModel(acc, accountID));
|
||||
data.add(new AccountViewModel(acc, accountID, getActivity()));
|
||||
}
|
||||
list.getAdapter().notifyItemRangeInserted(data.size()-accounts.size(), accounts.size());
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ public class NotificationRequestsFragment extends MastodonRecyclerFragment<Notif
|
||||
accountViewModels.clear();
|
||||
maxID=result.getNextPageMaxID();
|
||||
for(NotificationRequest req:result){
|
||||
accountViewModels.put(req.account.id, new AccountViewModel(req.account, accountID, false));
|
||||
accountViewModels.put(req.account.id, new AccountViewModel(req.account, accountID, false, getActivity()));
|
||||
}
|
||||
onDataLoaded(result, !TextUtils.isEmpty(maxID));
|
||||
endMark.setVisibility(TextUtils.isEmpty(maxID) ? View.VISIBLE : View.GONE);
|
||||
|
@ -580,7 +580,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
domain=AccountSessionManager.get(accountID).domain;
|
||||
usernameDomain.setText(domain);
|
||||
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
|
||||
CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account, getActivity());
|
||||
if(TextUtils.isEmpty(parsedBio)){
|
||||
bio.setVisibility(View.GONE);
|
||||
}else{
|
||||
@ -615,7 +615,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
||||
fields.add(joined);
|
||||
|
||||
for(AccountField field:account.fields){
|
||||
field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
|
||||
field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account, getActivity());
|
||||
field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class);
|
||||
ssb=new SpannableStringBuilder(field.name);
|
||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||
|
@ -61,7 +61,7 @@ public class AccountSearchFragment extends BaseAccountListFragment{
|
||||
|
||||
protected void onSuccess(List<Account> result){
|
||||
setEmptyText(R.string.no_search_results);
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), false);
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID, getActivity())).collect(Collectors.toList()), false);
|
||||
}
|
||||
|
||||
protected String getSearchViewPlaceholder(){
|
||||
|
@ -44,7 +44,7 @@ public class AddNewListMembersFragment extends AccountSearchFragment{
|
||||
@Override
|
||||
public void onSuccess(HeaderPaginationList<Account> result){
|
||||
setEmptyText("");
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), result.nextPageUri!=null);
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID, getActivity())).collect(Collectors.toList()), result.nextPageUri!=null);
|
||||
maxID=result.getNextPageMaxID();
|
||||
}
|
||||
})
|
||||
|
@ -24,7 +24,7 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
|
||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||
else
|
||||
nextMaxID=null;
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList()), nextMaxID!=null);
|
||||
onDataLoaded(result.stream().map(a->new AccountViewModel(a, accountID, getActivity())).collect(Collectors.toList()), nextMaxID!=null);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
@ -31,7 +31,7 @@ public class DiscoverAccountsFragment extends BaseAccountListFragment implements
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
List<AccountViewModel> accounts=result.stream().map(fs->new AccountViewModel(fs.account, accountID)).collect(Collectors.toList());
|
||||
List<AccountViewModel> accounts=result.stream().map(fs->new AccountViewModel(fs.account, accountID, getActivity())).collect(Collectors.toList());
|
||||
onDataLoaded(accounts, false);
|
||||
bannerHelper.onBannerBecameVisible();
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
return;
|
||||
|
||||
onDataLoaded(results.stream().map(sr->{
|
||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true);
|
||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, true, getActivity());
|
||||
if(sr.type==SearchResult.Type.HASHTAG){
|
||||
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
||||
}
|
||||
@ -126,7 +126,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
||||
onDataLoaded(Stream.of(result.hashtags.stream().map(SearchResult::new), result.accounts.stream().map(SearchResult::new))
|
||||
.flatMap(Function.identity())
|
||||
.map(sr->{
|
||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false, getActivity());
|
||||
if(sr.type==SearchResult.Type.HASHTAG){
|
||||
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<FollowSuggestion> result){
|
||||
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID).stripLinksFromBio()).collect(Collectors.toList()), false);
|
||||
onDataLoaded(result.stream().map(fs->new AccountViewModel(fs.account, accountID, getActivity()).stripLinksFromBio()).collect(Collectors.toList()), false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
@ -128,7 +128,7 @@ public class SettingsServerAboutFragment extends LoaderFragment{
|
||||
hlp.leftMargin=hlp.rightMargin=V.dp(16);
|
||||
scrollingLayout.addView(heading, hlp);
|
||||
|
||||
AccountViewModel model=new AccountViewModel(instance.getContactAccount(), accountID);
|
||||
AccountViewModel model=new AccountViewModel(instance.getContactAccount(), accountID, getActivity());
|
||||
AccountViewHolder holder=new AccountViewHolder(this, scrollingLayout, null);
|
||||
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||
holder.bind(model);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.model.viewmodel;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
||||
@ -24,11 +25,11 @@ public class AccountViewModel{
|
||||
public final CharSequence parsedName, parsedBio;
|
||||
public final String verifiedLink;
|
||||
|
||||
public AccountViewModel(Account account, String accountID){
|
||||
this(account, accountID, true);
|
||||
public AccountViewModel(Account account, String accountID, Context context){
|
||||
this(account, accountID, true, context);
|
||||
}
|
||||
|
||||
public AccountViewModel(Account account, String accountID, boolean needBio){
|
||||
public AccountViewModel(Account account, String accountID, boolean needBio, Context context){
|
||||
this.account=account;
|
||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
||||
emojiHelper=new CustomEmojiHelper();
|
||||
@ -38,7 +39,7 @@ public class AccountViewModel{
|
||||
parsedName=account.displayName;
|
||||
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
||||
if(needBio){
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account);
|
||||
parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID, account, context);
|
||||
ssb.append(parsedBio);
|
||||
}else{
|
||||
parsedBio=null;
|
||||
|
@ -11,10 +11,10 @@ public class SearchResultViewModel{
|
||||
public AccountViewModel account;
|
||||
public ListItem<Hashtag> hashtagItem;
|
||||
|
||||
public SearchResultViewModel(SearchResult result, String accountID, boolean isRecents){
|
||||
public SearchResultViewModel(SearchResult result, String accountID, boolean isRecents, Context context){
|
||||
this.result=result;
|
||||
switch(result.type){
|
||||
case ACCOUNT -> account=new AccountViewModel(result.account, accountID);
|
||||
case ACCOUNT -> account=new AccountViewModel(result.account, accountID, context);
|
||||
case HASHTAG -> {
|
||||
hashtagItem=new ListItem<>((isRecents ? "#" : "")+result.hashtag.name, null, isRecents ? R.drawable.ic_history_24px : R.drawable.ic_tag_24px, null, result.hashtag);
|
||||
hashtagItem.isEnabled=true;
|
||||
|
@ -15,7 +15,7 @@ public class AccountStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
public AccountStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account){
|
||||
super(parentID, parentFragment);
|
||||
this.account=new AccountViewModel(account, parentFragment.getAccountID());
|
||||
this.account=new AccountViewModel(account, parentFragment.getAccountID(), parentFragment.getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -39,7 +39,7 @@ public class InlineStatusStatusDisplayItem extends StatusDisplayItem{
|
||||
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
||||
HtmlParser.parseCustomEmoji(parsedName, status.account.emojis);
|
||||
|
||||
parsedPostText=HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, parentFragment.getAccountID(), status.getContentStatus());
|
||||
parsedPostText=HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, parentFragment.getAccountID(), status.getContentStatus(), parentFragment.getActivity());
|
||||
for(Object span:parsedPostText.getSpans(0, parsedPostText.length(), Object.class)){
|
||||
if(!(span instanceof CustomEmojiSpan))
|
||||
parsedPostText.removeSpan(span);
|
||||
|
@ -141,7 +141,7 @@ public abstract class StatusDisplayItem{
|
||||
}
|
||||
|
||||
if(!TextUtils.isEmpty(statusForContent.content)){
|
||||
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, statusForContent);
|
||||
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, statusForContent, fragment.getActivity());
|
||||
if(filtered){
|
||||
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
||||
|
||||
public void setTranslatedText(String text){
|
||||
Status statusForContent=status.getContentStatus();
|
||||
translatedText=HtmlParser.parse(text, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, parentFragment.getAccountID(), statusForContent);
|
||||
translatedText=HtmlParser.parse(text, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, parentFragment.getAccountID(), statusForContent, parentFragment.getActivity());
|
||||
translationEmojiHelper.setText(translatedText);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.TypefaceSpan;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public abstract class BaseMonospaceSpan extends TypefaceSpan{
|
||||
private final Context context;
|
||||
|
||||
public BaseMonospaceSpan(Context context){
|
||||
super("monospace");
|
||||
this.context=context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint paint){
|
||||
super.updateDrawState(paint);
|
||||
paint.setColor(UiUtils.getThemeColor(context, R.attr.colorM3Tertiary));
|
||||
paint.setTextSize(paint.getTextSize()*0.9375f);
|
||||
paint.baselineShift=V.dp(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMeasureState(@NonNull TextPaint paint){
|
||||
super.updateMeasureState(paint);
|
||||
paint.setTextSize(paint.getTextSize()*0.9375f);
|
||||
paint.baselineShift=V.dp(-1);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class CodeBlockSpan extends BaseMonospaceSpan{
|
||||
public CodeBlockSpan(Context context){
|
||||
super(context);
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class HtmlParser{
|
||||
private static final String TAG="HtmlParser";
|
||||
@ -72,7 +73,7 @@ public class HtmlParser{
|
||||
* @param emojis Custom emojis that are present in source as <code>:code:</code>
|
||||
* @return a spanned string
|
||||
*/
|
||||
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID, Object parentObject){
|
||||
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID, Object parentObject, Context context){
|
||||
class SpanInfo{
|
||||
public Object span;
|
||||
public int start;
|
||||
@ -95,10 +96,18 @@ public class HtmlParser{
|
||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||
private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
|
||||
|
||||
private boolean isInsidePre(){
|
||||
for(SpanInfo si:openSpans){
|
||||
if(si.span instanceof CodeBlockSpan)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void head(@NonNull Node node, int depth){
|
||||
if(node instanceof TextNode textNode){
|
||||
ssb.append(textNode.text());
|
||||
ssb.append(isInsidePre() ? textNode.getWholeText().stripTrailing() : textNode.text());
|
||||
}else if(node instanceof Element el){
|
||||
switch(el.nodeName()){
|
||||
case "a" -> {
|
||||
@ -137,6 +146,15 @@ public class HtmlParser{
|
||||
case "b", "strong" -> openSpans.add(new SpanInfo(new StyleSpan(Typeface.BOLD), ssb.length(), el));
|
||||
case "i", "em" -> openSpans.add(new SpanInfo(new StyleSpan(Typeface.ITALIC), ssb.length(), el));
|
||||
case "s", "del" -> openSpans.add(new SpanInfo(new StrikethroughSpan(), ssb.length(), el));
|
||||
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);
|
||||
}
|
||||
}
|
||||
case "pre" -> {
|
||||
openSpans.add(new SpanInfo(new CodeBlockSpan(context), ssb.length(), el));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,6 +170,9 @@ public class HtmlParser{
|
||||
}else if(!openSpans.isEmpty()){
|
||||
SpanInfo si=openSpans.get(openSpans.size()-1);
|
||||
if(si.element==el){
|
||||
if(si.span instanceof MonospaceSpan){
|
||||
ssb.append(" ", new SpacerSpan(V.dp(4), 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
ssb.setSpan(si.span, si.start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
openSpans.remove(openSpans.size()-1);
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class MonospaceSpan extends BaseMonospaceSpan{
|
||||
public MonospaceSpan(Context context){
|
||||
super(context);
|
||||
}
|
||||
}
|
@ -244,7 +244,7 @@ public class ComposeAutocompleteViewController{
|
||||
if(mode!=Mode.USERS)
|
||||
return;
|
||||
List<AccountViewModel> oldList=users;
|
||||
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID)).collect(Collectors.toList());
|
||||
users=result.accounts.stream().map(a->new AccountViewModel(a, accountID, activity)).collect(Collectors.toList());
|
||||
if(isLoading){
|
||||
isLoading=false;
|
||||
if(users.size()>=LOADING_FAKE_USER_COUNT){
|
||||
|
@ -4,6 +4,10 @@ import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.CornerPathEffect;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.text.Layout;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.util.AttributeSet;
|
||||
@ -14,14 +18,22 @@ import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.ui.text.ClickableLinksDelegate;
|
||||
import org.joinmastodon.android.ui.text.CodeBlockSpan;
|
||||
import org.joinmastodon.android.ui.text.DeleteWhenCopiedSpan;
|
||||
import org.joinmastodon.android.ui.text.MonospaceSpan;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class LinkedTextView extends TextView{
|
||||
|
||||
private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this);
|
||||
private boolean needInvalidate;
|
||||
private ActionMode currentActionMode;
|
||||
private Paint bgPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private Path tmpPath=new Path();
|
||||
|
||||
public LinkedTextView(Context context){
|
||||
this(context, null);
|
||||
@ -56,6 +68,9 @@ public class LinkedTextView extends TextView{
|
||||
currentActionMode=null;
|
||||
}
|
||||
});
|
||||
bgPaint.setColor(UiUtils.getThemeColor(context, R.attr.colorM3TertiaryContainer));
|
||||
bgPaint.setAlpha(20);
|
||||
bgPaint.setPathEffect(new CornerPathEffect(V.dp(2)));
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent ev){
|
||||
@ -64,6 +79,22 @@ public class LinkedTextView extends TextView{
|
||||
}
|
||||
|
||||
public void onDraw(Canvas c){
|
||||
if(getText() instanceof Spanned spanned){
|
||||
c.save();
|
||||
c.translate(getTotalPaddingLeft(), getTotalPaddingTop());
|
||||
Layout layout=getLayout();
|
||||
MonospaceSpan[] monospaceSpans=spanned.getSpans(0, spanned.length(), MonospaceSpan.class);
|
||||
for(MonospaceSpan span:monospaceSpans){
|
||||
layout.getSelectionPath(spanned.getSpanStart(span), spanned.getSpanEnd(span), tmpPath);
|
||||
c.drawPath(tmpPath, bgPaint);
|
||||
}
|
||||
CodeBlockSpan[] blockSpans=spanned.getSpans(0, spanned.length(), CodeBlockSpan.class);
|
||||
for(CodeBlockSpan span:blockSpans){
|
||||
c.drawRoundRect(V.dp(-4), layout.getLineTop(layout.getLineForOffset(spanned.getSpanStart(span)))-V.dp(8), layout.getWidth()+V.dp(4),
|
||||
layout.getLineBottom(layout.getLineForOffset(spanned.getSpanEnd(span)))+V.dp(4), V.dp(2), V.dp(2), bgPaint);
|
||||
}
|
||||
c.restore();
|
||||
}
|
||||
super.onDraw(c);
|
||||
delegate.onDraw(c);
|
||||
if(needInvalidate)
|
||||
|
Loading…
Reference in New Issue
Block a user