Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ad2d08e27 | ||
|
|
42658add38 | ||
|
|
b211789847 | ||
|
|
9c88183366 | ||
|
|
c76dba3a8c | ||
|
|
29bee87f2a | ||
|
|
c139f85b99 | ||
|
|
3247d4f2f5 | ||
|
|
77b2f98f17 | ||
|
|
82c6c8076a | ||
|
|
4177faa553 | ||
|
|
92ec125661 | ||
|
|
513a57663b | ||
|
|
20e7f716f1 |
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "org.joinmastodon.android"
|
||||
minSdk 23
|
||||
targetSdk 34
|
||||
versionCode 121
|
||||
versionName "2.7.2"
|
||||
versionCode 123
|
||||
versionName "2.7.3"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http"/>
|
||||
<data android:scheme="http" android:host="*"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https" android:host="*"/>
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
|
||||
@@ -295,6 +295,15 @@ public class CacheController{
|
||||
.collect(Collectors.toList());
|
||||
PaginatedResponse<List<NotificationViewModel>> res=new PaginatedResponse<>(converted, result.isEmpty() ? null : result.get(result.size()-1).id);
|
||||
callback.onSuccess(res);
|
||||
if(!onlyMentions){
|
||||
loadingNotifications=false;
|
||||
synchronized(pendingNotificationsCallbacks){
|
||||
for(Callback<PaginatedResponse<List<NotificationViewModel>>> cb:pendingNotificationsCallbacks){
|
||||
cb.onSuccess(res);
|
||||
}
|
||||
pendingNotificationsCallbacks.clear();
|
||||
}
|
||||
}
|
||||
databaseThread.postRunnable(()->putNotifications(converted.stream().map(nvm->nvm.notification).collect(Collectors.toList()), accounts, statuses, onlyMentions, maxID==null), 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
package org.joinmastodon.android.api.requests.notifications;
|
||||
|
||||
import org.joinmastodon.android.api.ApiUtils;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.model.NotificationType;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
public class GetUnreadNotificationsCount extends MastodonAPIRequest<GetUnreadNotificationsCount.Response>{
|
||||
public GetUnreadNotificationsCount(){
|
||||
public GetUnreadNotificationsCount(EnumSet<NotificationType> includeTypes, EnumSet<NotificationType> groupedTypes){
|
||||
super(HttpMethod.GET, "/notifications/unread_count", Response.class);
|
||||
if(includeTypes!=null){
|
||||
for(String type: ApiUtils.enumSetToStrings(includeTypes, NotificationType.class)){
|
||||
addQueryParameter("types[]", type);
|
||||
}
|
||||
for(String type:ApiUtils.enumSetToStrings(EnumSet.complementOf(includeTypes), NotificationType.class)){
|
||||
addQueryParameter("exclude_types[]", type);
|
||||
}
|
||||
}
|
||||
if(groupedTypes!=null){
|
||||
for(String type:ApiUtils.enumSetToStrings(groupedTypes, NotificationType.class)){
|
||||
addQueryParameter("grouped_types[]", type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -296,7 +296,7 @@ public class HomeFragment extends AppKitFragment{
|
||||
if(instance==null)
|
||||
return;
|
||||
if(instance.getApiVersion()>=2){
|
||||
new GetUnreadNotificationsCount()
|
||||
new GetUnreadNotificationsCount(EnumSet.allOf(NotificationType.class), NotificationType.getGroupableTypes())
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(GetUnreadNotificationsCount.Response result){
|
||||
|
||||
@@ -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.colorRichTextText));
|
||||
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,83 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
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()).mutate();
|
||||
this.firstLevel=firstLevel;
|
||||
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 color;
|
||||
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.S && UiUtils.isDarkTheme()){
|
||||
color=UiUtils.alphaBlendColors(
|
||||
context.getColor(android.R.color.system_accent3_700),
|
||||
context.getColor(android.R.color.system_accent3_800),
|
||||
0.5f
|
||||
);
|
||||
}else{
|
||||
color=UiUtils.getThemeColor(context, R.attr.colorRichTextDecorations);
|
||||
}
|
||||
int level=s.getSpans(start, end, LeadingMarginSpan.class).length-1;
|
||||
if(dir<0){ // RTL
|
||||
if(level==0){
|
||||
icon.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
icon.setBounds(layout.getWidth()-icon.getIntrinsicWidth(), top, layout.getWidth(), top+icon.getIntrinsicHeight());
|
||||
icon.draw(c);
|
||||
}else{
|
||||
paint.setColor(color);
|
||||
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.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
icon.setBounds(x, top, x+icon.getIntrinsicWidth(), top+icon.getIntrinsicHeight());
|
||||
icon.draw(c);
|
||||
}else{
|
||||
paint.setColor(color);
|
||||
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.colorRichTextText));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.SpannableStringBuilder;
|
||||
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;
|
||||
|
||||
import com.twitter.twittertext.Regex;
|
||||
@@ -34,6 +40,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";
|
||||
@@ -69,7 +76,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;
|
||||
@@ -92,10 +99,34 @@ 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;
|
||||
}
|
||||
|
||||
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){
|
||||
if(node instanceof TextNode textNode){
|
||||
ssb.append(textNode.text());
|
||||
if(isInsidePre()){
|
||||
ssb.append(textNode.getWholeText().stripTrailing());
|
||||
}else{
|
||||
String text=textNode.text();
|
||||
if(ssb.length()==0 || ssb.charAt(ssb.length()-1)=='\n')
|
||||
text=text.stripLeading();
|
||||
ssb.append(text);
|
||||
}
|
||||
}else if(node instanceof Element el){
|
||||
switch(el.nodeName()){
|
||||
case "a" -> {
|
||||
@@ -131,6 +162,44 @@ public class HtmlParser{
|
||||
openSpans.add(new SpanInfo(new InvisibleSpan(), 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 "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), 0), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
case "pre" -> openSpans.add(new SpanInfo(new CodeBlockSpan(context), ssb.length(), el));
|
||||
case "li" -> {
|
||||
Element parent=el.parent();
|
||||
if(parent==null)
|
||||
return;
|
||||
|
||||
if(ssb.length()>0 && ssb.charAt(ssb.length()-1)!='\n')
|
||||
ssb.append('\n');
|
||||
String markerText;
|
||||
if("ol".equals(parent.nodeName())){
|
||||
markerText=String.format("%d.", (parent.hasAttr("start") ? safeParseInt(parent.attr("start")) : 1)+el.elementSiblingIndex());
|
||||
}else{
|
||||
markerText="•";
|
||||
}
|
||||
openSpans.add(new SpanInfo(new ListItemMarkerSpan(markerText), ssb.length(), el));
|
||||
StringBuilder copyableText=new StringBuilder();
|
||||
for(SpanInfo si:openSpans){
|
||||
if(si.span instanceof ListItemMarkerSpan ims){
|
||||
copyableText.append(ims.text);
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,15 +207,27 @@ public class HtmlParser{
|
||||
@Override
|
||||
public void tail(@NonNull Node node, int depth){
|
||||
if(node instanceof Element el){
|
||||
if("span".equals(el.nodeName()) && el.hasClass("ellipsis")){
|
||||
String name=el.nodeName();
|
||||
if("span".equals(name) && el.hasClass("ellipsis")){
|
||||
ssb.append("…", new DeleteWhenCopiedSpan(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}else if("p".equals(el.nodeName())){
|
||||
}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\n");
|
||||
}else if(!openSpans.isEmpty()){
|
||||
ssb.append("\n");
|
||||
}
|
||||
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);
|
||||
if(si.span!=null){
|
||||
if(si.span instanceof MonospaceSpan){
|
||||
ssb.append(" ", new SpacerSpan(V.dp(4), 0), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
ssb.setSpan(si.span, si.start, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
openSpans.remove(openSpans.size()-1);
|
||||
}
|
||||
}
|
||||
@@ -158,6 +239,14 @@ public class HtmlParser{
|
||||
return ssb;
|
||||
}
|
||||
|
||||
private static int safeParseInt(String s){
|
||||
try{
|
||||
return Integer.parseInt(s);
|
||||
}catch(NumberFormatException x){
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){
|
||||
Map<String, Emoji> emojiByCode =
|
||||
emojis.stream()
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.text.Layout;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.LeadingMarginSpan;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ListItemMarkerSpan implements LeadingMarginSpan{
|
||||
public String text;
|
||||
|
||||
public ListItemMarkerSpan(String text){
|
||||
this.text=text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLeadingMargin(boolean first){
|
||||
return V.dp(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, 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);
|
||||
}else{
|
||||
c.drawText(this.text, x+V.dp(32*level), baseline, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 @@ public class LinkedTextView extends TextView{
|
||||
currentActionMode=null;
|
||||
}
|
||||
});
|
||||
bgPaint.setColor(UiUtils.getThemeColor(context, R.attr.colorRichTextContainer));
|
||||
bgPaint.setPathEffect(new CornerPathEffect(V.dp(2)));
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent ev){
|
||||
@@ -64,6 +78,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)
|
||||
|
||||
9
mastodon/src/main/res/drawable/quote.xml
Normal file
9
mastodon/src/main/res/drawable/quote.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<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="#000"
|
||||
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>
|
||||
@@ -774,8 +774,14 @@ Zenbat eta jende gehiago jarraitu, orduan eta aktiboagoa eta interesgarriagoa iz
|
||||
<item quantity="one">Erantsitako fitxategi %d</item>
|
||||
<item quantity="other">%d erantsitako fitxategi</item>
|
||||
</plurals>
|
||||
<plurals name="poll_ended_x_voters">
|
||||
<item quantity="one">%1$s erabiltzaileak inkesta bat egin zuen eta zuk eta beste erabiltzaile %2$,d bozkatu duzue</item>
|
||||
<item quantity="other">%1$s erabiltzaileak inkesta bat egin zuen eta zuk eta beste %2$,d erabiltzailek bozkatu duzue</item>
|
||||
</plurals>
|
||||
<string name="own_poll_ended">Zure inkesta amaitu da</string>
|
||||
<string name="user_just_posted">%s erabiltzaileak bidalketa egin berri du</string>
|
||||
<string name="user_edited_post">%s(e)k interaktuatu zenuen argitalpen bat editatu du</string>
|
||||
<string name="relationship_severance_account_suspension">%1$s zerbitzariko administratzaile batek %2$s bertan behera utzi du, hau da, ezin izango dituzu jaso hango eguneratzerik edo hangoekin elkarreragin.</string>
|
||||
<!-- %1$s is your server domain, %2$s is the domain that was blocked, %3$,d is the follower count, %4$s is the `x_accounts` plural string -->
|
||||
<plurals name="x_accounts">
|
||||
<item quantity="one">kontu %,d</item>
|
||||
@@ -789,5 +795,6 @@ Zenbat eta jende gehiago jarraitu, orduan eta aktiboagoa eta interesgarriagoa iz
|
||||
<string name="moderation_warning_action_delete_statuses">Zure argitalpen batzuk kendu dira.</string>
|
||||
<string name="moderation_warning_action_sensitive">Zure bidalketak hunkigarri gisa markatuko dira aurrerantzean.</string>
|
||||
<string name="moderation_warning_action_silence">Zure kontuari mugak jarri zaizkio.</string>
|
||||
<string name="moderation_warning_action_suspend">Zure kontua behin-behinean egotzi da.</string>
|
||||
<string name="moderation_warning_learn_more">Informazio gehiago</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<string name="notifications">Notifikasi</string>
|
||||
<string name="user_followed_you">%s mengikuti Anda</string>
|
||||
<string name="user_sent_follow_request">%s mengirim Anda permintaan pengikut</string>
|
||||
<string name="user_favorited">%s memfavorit:</string>
|
||||
<string name="notification_boosted">%s membagikan ulang:</string>
|
||||
<string name="share_toot_title">Bagikan</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="publish">Terbitkan</string>
|
||||
@@ -730,6 +732,39 @@
|
||||
<string name="donation_server_error">Maafkan kami, kesalahan telah terjadi dan kami belum bisa memproses donasi anda.\n\nMohon ulang kembali beberapa menit lagi.</string>
|
||||
<string name="settings_donate">Donasi ke Mastodon</string>
|
||||
<string name="settings_manage_donations">Kelola donasi</string>
|
||||
<string name="cant_load_image">Tidak dapat memuat gambar</string>
|
||||
<string name="poll_see_results">Lihat hasil</string>
|
||||
<string name="poll_hide_results">Sembunyikan hasil</string>
|
||||
<plurals name="x_attachments">
|
||||
<item quantity="other">%d lampiran</item>
|
||||
</plurals>
|
||||
<plurals name="user_and_x_more_favorited">
|
||||
<item quantity="other">%1$s dan %2$,d lainnya memfavorit:</item>
|
||||
</plurals>
|
||||
<plurals name="user_and_x_more_boosted">
|
||||
<item quantity="other">%1$s dan %2$,d lainnya membagikan ulang:</item>
|
||||
</plurals>
|
||||
<plurals name="poll_ended_x_voters">
|
||||
<item quantity="other">%1$s memulai japat yang Anda dan %2$,d lainnya beri suara</item>
|
||||
</plurals>
|
||||
<string name="own_poll_ended">Japat Anda telah berakhir</string>
|
||||
<string name="user_just_posted">%s baru saja mengirim</string>
|
||||
<string name="user_edited_post">%s menyunting kiriman yang Anda interaksi</string>
|
||||
<string name="relationship_severance_account_suspension">Seorang admin dari %1$s telah menangguhkan %2$s, yang berarti Anda tidak dapat lagi menerima pembaruan atau berinteraksi.</string>
|
||||
<!-- %1$s is your server domain, %2$s is the domain that was blocked, %3$,d is the follower count, %4$s is the `x_accounts` plural string -->
|
||||
<string name="relationship_severance_domain_block">Seorang admin dari %1$s telah memblokir %2$s, termasuk %3$,d dari pengikut Anda dan %4$s lain yang Anda ikuti.</string>
|
||||
<plurals name="x_accounts">
|
||||
<item quantity="other">%,d akun</item>
|
||||
</plurals>
|
||||
<!-- %1$s is the domain that was blocked, %2$,d is the follower count, %3$s is the `x_accounts` plural string -->
|
||||
<string name="relationship_severance_user_domain_block">Anda telah memblokir %1$s, menghapus %2$,d dari pengikut Anda dan %3$s lain yang Anda ikuti.</string>
|
||||
<string name="relationship_severance_learn_more">Pelajari lebih lanjut</string>
|
||||
<string name="moderation_warning_action_none">Akun Anda telah menerima peringatan moderasi.</string>
|
||||
<string name="moderation_warning_action_disable">Akun Anda telah dinonaktifkan.</string>
|
||||
<string name="moderation_warning_action_mark_statuses_as_sensitive">Beberapa kiriman Anda telah ditandai sebagai sensitif.</string>
|
||||
<string name="moderation_warning_action_delete_statuses">Beberapa kiriman Anda telah dihapus.</string>
|
||||
<string name="moderation_warning_action_sensitive">Kiriman Anda akan ditandai sebagai sensitif mulai dari sekarang.</string>
|
||||
<string name="moderation_warning_action_silence">Akun Anda telah dibatasi.</string>
|
||||
<string name="moderation_warning_action_suspend">Akun Anda telah ditangguhkan.</string>
|
||||
<string name="moderation_warning_learn_more">Pelajari lebih lanjut</string>
|
||||
</resources>
|
||||
|
||||
@@ -464,7 +464,7 @@
|
||||
<string name="notifications_policy_no_one">Никого</string>
|
||||
<string name="settings_notifications_policy">Получать уведомления от</string>
|
||||
<string name="notification_type_mentions_and_replies">Упоминания и ответы</string>
|
||||
<string name="pause_all_notifications_title">Просмотреть все уведомления</string>
|
||||
<string name="pause_all_notifications_title">Приостановить все уведомления</string>
|
||||
<plurals name="x_weeks">
|
||||
<item quantity="one">%d неделя</item>
|
||||
<item quantity="few">%d недели</item>
|
||||
|
||||
@@ -45,4 +45,11 @@
|
||||
<color name="m3_sys_dark_on_surface_variant">@android:color/system_neutral2_200</color>
|
||||
<color name="m3_sys_dark_outline">@android:color/system_neutral2_400</color>
|
||||
<color name="m3_sys_dark_outline_variant">@android:color/system_neutral2_700</color>
|
||||
|
||||
<color name="ext_rich_text_text_light">@android:color/system_accent3_700</color>
|
||||
<color name="ext_rich_text_container_light">@android:color/system_accent3_100</color>
|
||||
<color name="ext_rich_text_decoration_light">@android:color/system_accent3_200</color>
|
||||
<color name="ext_rich_text_text_dark">@android:color/system_accent3_200</color>
|
||||
<color name="ext_rich_text_container_dark">@android:color/system_accent3_800</color>
|
||||
<color name="ext_rich_text_decoration_dark">#0f0</color> <!-- it's "tertiary 35" but oh well -->
|
||||
</resources>
|
||||
|
||||
@@ -276,7 +276,7 @@
|
||||
<string name="file_size_gb">%.2f GB</string>
|
||||
<string name="upload_processing">正在处理…</string>
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">已下载(%s)</string>
|
||||
<string name="download_update">下载(%s)</string>
|
||||
<string name="install_update">安装</string>
|
||||
<string name="privacy_policy_title">你的隐私</string>
|
||||
<string name="privacy_policy_subtitle">尽管 Mastodon 客户端本身不采集任何数据,你注册的实例可能会有不同的隐私政策。\n\n如果你不同意 %s 的这些政策,请返回并选择其他实例。</string>
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
<attr name="colorWhite" format="color"/>
|
||||
<attr name="colorFavorite" format="color" />
|
||||
<attr name="colorBoost" format="color" />
|
||||
<attr name="colorRichTextText" format="color"/>
|
||||
<attr name="colorRichTextContainer" format="color"/>
|
||||
<attr name="colorRichTextDecorations" format="color"/>
|
||||
|
||||
<declare-styleable name="MaxWidthFrameLayout">
|
||||
<attr name="android:maxWidth" format="dimension"/>
|
||||
|
||||
@@ -104,5 +104,12 @@
|
||||
<color name="ext_on_bookmark_container_light_medium_contrast">#FFFFFF</color>
|
||||
<color name="ext_on_bookmark_container_dark">#FFFFFF</color>
|
||||
|
||||
<color name="ext_rich_text_text_light">#6C3646</color>
|
||||
<color name="ext_rich_text_container_light">#FFD9E1</color>
|
||||
<color name="ext_rich_text_decoration_light">#FDB2C5</color>
|
||||
<color name="ext_rich_text_text_dark">#FDB2C5</color>
|
||||
<color name="ext_rich_text_container_dark">#512030</color>
|
||||
<color name="ext_rich_text_decoration_dark">#7A4152</color>
|
||||
|
||||
<item name="overlay_ripple_alpha" format="float" type="dimen">0.12</item>
|
||||
</resources>
|
||||
@@ -58,6 +58,9 @@
|
||||
<item name="colorWhite">#FFF</item>
|
||||
<item name="colorFavorite">@color/ext_favorite_light</item>
|
||||
<item name="colorBoost">@color/ext_boost_light</item>
|
||||
<item name="colorRichTextText">@color/ext_rich_text_text_light</item>
|
||||
<item name="colorRichTextContainer">@color/ext_rich_text_container_light</item>
|
||||
<item name="colorRichTextDecorations">@color/ext_rich_text_decoration_light</item>
|
||||
|
||||
<item name="android:statusBarColor">?colorM3Background</item>
|
||||
<item name="android:navigationBarColor">@color/navigation_bar_bg_light</item>
|
||||
@@ -126,6 +129,9 @@
|
||||
<item name="colorWhite">#000</item>
|
||||
<item name="colorFavorite">@color/ext_favorite_dark</item>
|
||||
<item name="colorBoost">@color/ext_boost_dark</item>
|
||||
<item name="colorRichTextText">@color/ext_rich_text_text_dark</item>
|
||||
<item name="colorRichTextContainer">@color/ext_rich_text_container_dark</item>
|
||||
<item name="colorRichTextDecorations">@color/ext_rich_text_decoration_dark</item>
|
||||
|
||||
<item name="android:statusBarColor">?colorM3Background</item>
|
||||
<item name="android:navigationBarColor">?colorM3Background</item>
|
||||
|
||||
Reference in New Issue
Block a user