Monospace text formatting (AND-223)
This commit is contained in:
@@ -90,7 +90,7 @@ dependencies {
|
|||||||
implementation 'me.grishka.litex:viewpager:1.0.0'
|
implementation 'me.grishka.litex:viewpager:1.0.0'
|
||||||
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
implementation 'me.grishka.litex:viewpager2:1.0.0'
|
||||||
implementation 'me.grishka.litex:palette: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 'com.google.code.gson:gson:2.8.9'
|
||||||
implementation 'org.jsoup:jsoup:1.14.3'
|
implementation 'org.jsoup:jsoup:1.14.3'
|
||||||
implementation 'com.squareup:otto:1.3.8'
|
implementation 'com.squareup:otto:1.3.8'
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ public class CreateListAddMembersFragment extends BaseAccountListFragment implem
|
|||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
public void onSuccess(HeaderPaginationList<Account> result){
|
||||||
for(Account acc:result)
|
for(Account acc:result)
|
||||||
accountIDsInList.add(acc.id);
|
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);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
|||||||
@Subscribe
|
@Subscribe
|
||||||
public void onAccountAddedToList(AccountAddedToListEvent ev){
|
public void onAccountAddedToList(AccountAddedToListEvent ev){
|
||||||
if(ev.accountID.equals(accountID) && ev.listID.equals(followList.id)){
|
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);
|
list.getAdapter().notifyItemInserted(data.size()-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,7 +337,7 @@ public class ListMembersFragment extends PaginatedAccountListFragment implements
|
|||||||
onDone.run();
|
onDone.run();
|
||||||
for(Account acc:accounts){
|
for(Account acc:accounts){
|
||||||
accountIDsInList.add(acc.id);
|
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());
|
list.getAdapter().notifyItemRangeInserted(data.size()-accounts.size(), accounts.size());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class NotificationRequestsFragment extends MastodonRecyclerFragment<Notif
|
|||||||
accountViewModels.clear();
|
accountViewModels.clear();
|
||||||
maxID=result.getNextPageMaxID();
|
maxID=result.getNextPageMaxID();
|
||||||
for(NotificationRequest req:result){
|
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));
|
onDataLoaded(result, !TextUtils.isEmpty(maxID));
|
||||||
endMark.setVisibility(TextUtils.isEmpty(maxID) ? View.VISIBLE : View.GONE);
|
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;
|
domain=AccountSessionManager.get(accountID).domain;
|
||||||
usernameDomain.setText(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)){
|
if(TextUtils.isEmpty(parsedBio)){
|
||||||
bio.setVisibility(View.GONE);
|
bio.setVisibility(View.GONE);
|
||||||
}else{
|
}else{
|
||||||
@@ -615,7 +615,7 @@ public class ProfileFragment extends LoaderFragment implements ScrollableToTop{
|
|||||||
fields.add(joined);
|
fields.add(joined);
|
||||||
|
|
||||||
for(AccountField field:account.fields){
|
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);
|
field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class);
|
||||||
ssb=new SpannableStringBuilder(field.name);
|
ssb=new SpannableStringBuilder(field.name);
|
||||||
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
HtmlParser.parseCustomEmoji(ssb, account.emojis);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class AccountSearchFragment extends BaseAccountListFragment{
|
|||||||
|
|
||||||
protected void onSuccess(List<Account> result){
|
protected void onSuccess(List<Account> result){
|
||||||
setEmptyText(R.string.no_search_results);
|
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(){
|
protected String getSearchViewPlaceholder(){
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class AddNewListMembersFragment extends AccountSearchFragment{
|
|||||||
@Override
|
@Override
|
||||||
public void onSuccess(HeaderPaginationList<Account> result){
|
public void onSuccess(HeaderPaginationList<Account> result){
|
||||||
setEmptyText("");
|
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();
|
maxID=result.getNextPageMaxID();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public abstract class PaginatedAccountListFragment extends BaseAccountListFragme
|
|||||||
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
nextMaxID=result.nextPageUri.getQueryParameter("max_id");
|
||||||
else
|
else
|
||||||
nextMaxID=null;
|
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);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class DiscoverAccountsFragment extends BaseAccountListFragment implements
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<FollowSuggestion> result){
|
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);
|
onDataLoaded(accounts, false);
|
||||||
bannerHelper.onBannerBecameVisible();
|
bannerHelper.onBannerBecameVisible();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class SearchQueryFragment extends MastodonRecyclerFragment<SearchResultVi
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
onDataLoaded(results.stream().map(sr->{
|
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){
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
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))
|
onDataLoaded(Stream.of(result.hashtags.stream().map(SearchResult::new), result.accounts.stream().map(SearchResult::new))
|
||||||
.flatMap(Function.identity())
|
.flatMap(Function.identity())
|
||||||
.map(sr->{
|
.map(sr->{
|
||||||
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false);
|
SearchResultViewModel vm=new SearchResultViewModel(sr, accountID, false, getActivity());
|
||||||
if(sr.type==SearchResult.Type.HASHTAG){
|
if(sr.type==SearchResult.Type.HASHTAG){
|
||||||
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
vm.hashtagItem.setOnClick(i->openHashtag(sr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public class OnboardingFollowSuggestionsFragment extends BaseAccountListFragment
|
|||||||
.setCallback(new SimpleCallback<>(this){
|
.setCallback(new SimpleCallback<>(this){
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(List<FollowSuggestion> result){
|
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);
|
.exec(accountID);
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ public class SettingsServerAboutFragment extends LoaderFragment{
|
|||||||
hlp.leftMargin=hlp.rightMargin=V.dp(16);
|
hlp.leftMargin=hlp.rightMargin=V.dp(16);
|
||||||
scrollingLayout.addView(heading, hlp);
|
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);
|
AccountViewHolder holder=new AccountViewHolder(this, scrollingLayout, null);
|
||||||
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
holder.setStyle(AccountViewHolder.AccessoryType.NONE, false);
|
||||||
holder.bind(model);
|
holder.bind(model);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.joinmastodon.android.model.viewmodel;
|
package org.joinmastodon.android.model.viewmodel;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
|
|
||||||
@@ -24,11 +25,11 @@ public class AccountViewModel{
|
|||||||
public final CharSequence parsedName, parsedBio;
|
public final CharSequence parsedName, parsedBio;
|
||||||
public final String verifiedLink;
|
public final String verifiedLink;
|
||||||
|
|
||||||
public AccountViewModel(Account account, String accountID){
|
public AccountViewModel(Account account, String accountID, Context context){
|
||||||
this(account, accountID, true);
|
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;
|
this.account=account;
|
||||||
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
avaRequest=new UrlImageLoaderRequest(GlobalUserPreferences.playGifs ? account.avatar : account.avatarStatic, V.dp(50), V.dp(50));
|
||||||
emojiHelper=new CustomEmojiHelper();
|
emojiHelper=new CustomEmojiHelper();
|
||||||
@@ -38,7 +39,7 @@ public class AccountViewModel{
|
|||||||
parsedName=account.displayName;
|
parsedName=account.displayName;
|
||||||
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
SpannableStringBuilder ssb=new SpannableStringBuilder(parsedName);
|
||||||
if(needBio){
|
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);
|
ssb.append(parsedBio);
|
||||||
}else{
|
}else{
|
||||||
parsedBio=null;
|
parsedBio=null;
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ public class SearchResultViewModel{
|
|||||||
public AccountViewModel account;
|
public AccountViewModel account;
|
||||||
public ListItem<Hashtag> hashtagItem;
|
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;
|
this.result=result;
|
||||||
switch(result.type){
|
switch(result.type){
|
||||||
case ACCOUNT -> account=new AccountViewModel(result.account, accountID);
|
case ACCOUNT -> account=new AccountViewModel(result.account, accountID, context);
|
||||||
case HASHTAG -> {
|
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=new ListItem<>((isRecents ? "#" : "")+result.hashtag.name, null, isRecents ? R.drawable.ic_history_24px : R.drawable.ic_tag_24px, null, result.hashtag);
|
||||||
hashtagItem.isEnabled=true;
|
hashtagItem.isEnabled=true;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class AccountStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
public AccountStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account){
|
public AccountStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Account account){
|
||||||
super(parentID, parentFragment);
|
super(parentID, parentFragment);
|
||||||
this.account=new AccountViewModel(account, parentFragment.getAccountID());
|
this.account=new AccountViewModel(account, parentFragment.getAccountID(), parentFragment.getActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public class InlineStatusStatusDisplayItem extends StatusDisplayItem{
|
|||||||
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
if(AccountSessionManager.get(parentFragment.getAccountID()).getLocalPreferences().customEmojiInNames)
|
||||||
HtmlParser.parseCustomEmoji(parsedName, status.account.emojis);
|
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)){
|
for(Object span:parsedPostText.getSpans(0, parsedPostText.length(), Object.class)){
|
||||||
if(!(span instanceof CustomEmojiSpan))
|
if(!(span instanceof CustomEmojiSpan))
|
||||||
parsedPostText.removeSpan(span);
|
parsedPostText.removeSpan(span);
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public abstract class StatusDisplayItem{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!TextUtils.isEmpty(statusForContent.content)){
|
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){
|
if(filtered){
|
||||||
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
|
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
|
|||||||
|
|
||||||
public void setTranslatedText(String text){
|
public void setTranslatedText(String text){
|
||||||
Status statusForContent=status.getContentStatus();
|
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);
|
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 java.util.stream.Collectors;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import me.grishka.appkit.utils.V;
|
||||||
|
|
||||||
public class HtmlParser{
|
public class HtmlParser{
|
||||||
private static final String TAG="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>
|
* @param emojis Custom emojis that are present in source as <code>:code:</code>
|
||||||
* @return a spanned string
|
* @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{
|
class SpanInfo{
|
||||||
public Object span;
|
public Object span;
|
||||||
public int start;
|
public int start;
|
||||||
@@ -95,10 +96,18 @@ public class HtmlParser{
|
|||||||
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
|
||||||
private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
|
private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
|
||||||
|
|
||||||
|
private boolean isInsidePre(){
|
||||||
|
for(SpanInfo si:openSpans){
|
||||||
|
if(si.span instanceof CodeBlockSpan)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void head(@NonNull Node node, int depth){
|
public void head(@NonNull Node node, int depth){
|
||||||
if(node instanceof TextNode textNode){
|
if(node instanceof TextNode textNode){
|
||||||
ssb.append(textNode.text());
|
ssb.append(isInsidePre() ? textNode.getWholeText().stripTrailing() : textNode.text());
|
||||||
}else if(node instanceof Element el){
|
}else if(node instanceof Element el){
|
||||||
switch(el.nodeName()){
|
switch(el.nodeName()){
|
||||||
case "a" -> {
|
case "a" -> {
|
||||||
@@ -137,6 +146,15 @@ public class HtmlParser{
|
|||||||
case "b", "strong" -> openSpans.add(new SpanInfo(new StyleSpan(Typeface.BOLD), ssb.length(), el));
|
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 "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 "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()){
|
}else if(!openSpans.isEmpty()){
|
||||||
SpanInfo si=openSpans.get(openSpans.size()-1);
|
SpanInfo si=openSpans.get(openSpans.size()-1);
|
||||||
if(si.element==el){
|
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);
|
ssb.setSpan(si.span, si.start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
openSpans.remove(openSpans.size()-1);
|
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)
|
if(mode!=Mode.USERS)
|
||||||
return;
|
return;
|
||||||
List<AccountViewModel> oldList=users;
|
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){
|
if(isLoading){
|
||||||
isLoading=false;
|
isLoading=false;
|
||||||
if(users.size()>=LOADING_FAKE_USER_COUNT){
|
if(users.size()>=LOADING_FAKE_USER_COUNT){
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import android.content.ClipData;
|
|||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
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.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
@@ -14,14 +18,22 @@ import android.view.MenuItem;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.ui.text.ClickableLinksDelegate;
|
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.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{
|
public class LinkedTextView extends TextView{
|
||||||
|
|
||||||
private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this);
|
private ClickableLinksDelegate delegate=new ClickableLinksDelegate(this);
|
||||||
private boolean needInvalidate;
|
private boolean needInvalidate;
|
||||||
private ActionMode currentActionMode;
|
private ActionMode currentActionMode;
|
||||||
|
private Paint bgPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private Path tmpPath=new Path();
|
||||||
|
|
||||||
public LinkedTextView(Context context){
|
public LinkedTextView(Context context){
|
||||||
this(context, null);
|
this(context, null);
|
||||||
@@ -56,6 +68,9 @@ public class LinkedTextView extends TextView{
|
|||||||
currentActionMode=null;
|
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){
|
public boolean onTouchEvent(MotionEvent ev){
|
||||||
@@ -64,6 +79,22 @@ public class LinkedTextView extends TextView{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onDraw(Canvas c){
|
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);
|
super.onDraw(c);
|
||||||
delegate.onDraw(c);
|
delegate.onDraw(c);
|
||||||
if(needInvalidate)
|
if(needInvalidate)
|
||||||
|
|||||||
Reference in New Issue
Block a user