Compare commits

...

16 Commits

Author SHA1 Message Date
sk
c1e67c4f73 bump version 2022-06-08 21:44:39 +02:00
sk
e0e48f87eb Merge branch 'master' into fork 2022-06-08 21:42:42 +02:00
Grishka
b2db64022f Add pre-upload avatar and header resizing 2022-06-06 16:45:56 +03:00
Samuel Kaiser
0ec14fe8fa Merge pull request #20 from Y32Gcnte8z/fork
fix simplified Chinese strings
2022-06-05 12:03:08 +02:00
Y32Gcnte8z
01a2f1d95c fix simplified Chinese strings 2022-06-04 13:20:56 +08:00
Samuel Kaiser
67b3e85837 Merge pull request #19 from Y32Gcnte8z/fork
New translations strings.xml (Chinese Simplified)
2022-06-03 11:31:19 +02:00
Y32Gcnte8z
9f4d330ab1 New translations strings.xml (Chinese Simplified) 2022-06-03 13:55:49 +08:00
sk
25092fbcfb add icon to readme 2022-05-31 17:04:58 +02:00
sk
705e98729d initial pink branding 2022-05-31 16:51:05 +02:00
sk
108d16a157 bump version 2022-05-26 22:44:31 +02:00
sk
e55ca6cc05 Merge branch 'feature/delete-redraft' into fork 2022-05-26 22:42:02 +02:00
sk
b8be1f184d hide redraft button when not applicable 2022-05-26 22:38:56 +02:00
sk
aa96ec54a3 Merge branch 'feature/delete-redraft' into fork 2022-05-26 22:09:39 +02:00
sk
e8b43c7179 preserve visibility when re-drafting 2022-05-26 22:09:02 +02:00
sk
b51b4a10ee Merge branch 'feature/delete-redraft' into fork 2022-05-26 21:44:07 +02:00
sk
f2b9ede27c Add proguard rules for parceler
according to http://parceler.org/
fixes issue where parceler can't find parcelable class
2022-05-26 21:43:39 +02:00
32 changed files with 404 additions and 139 deletions

View File

@@ -1,8 +1,6 @@
# Forked Mastodon for Android
![Pink version of the Mastodon for Android launcher icon](mastodon/src/main/res/mipmap-xhdpi/ic_launcher_round.png)
This is the repository for an officially forked Android app for Mastodon.
Learn more about the official app in the [blog post](https://blog.joinmastodon.org/2022/02/official-mastodon-for-android-app-is-coming-soon/).
# Mastodon for Android Fork
## Changes
@@ -18,6 +16,19 @@ Learn more about the official app in the [blog post](https://blog.joinmastodon.o
* [Implement a bookmark button and list](https://github.com/sk22/mastodon-android-fork/tree/feature/bookmarks) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/22))
* [Implement deleting and re-drafting](https://github.com/sk22/mastodon-android-fork/tree/feature/delete-redraft) ([Fixes issue](https://github.com/mastodon/mastodon-android/issues/21))
## Fork-specific changes
* Custom app name
* Custom icon: Modulate upstream icon's hue by `161%` using ImageMagick
```bash
mogrify -modulate 100,100,161 mastodon/src/main/res/mipmap-*/ic_launcher*.png
```
* Custom primary color: Hue of all `primary` colors in `colors.xml` is rotated
by `109.8°` (equivalent of `161%`, done by hand using
[PineTools](https://pinetools.com/shift-hue-color))
## Building
As this app is using Java 17 features, you need JDK 17 or newer to build it. Other than that, everything is pretty standard. You can either import the project into Android Studio and build it from there, or run the following command in the project directory:

View File

@@ -9,8 +9,8 @@ android {
applicationId "org.joinmastodon.android.sk"
minSdk 23
targetSdk 31
versionCode 15
versionName '1.1.1+fork.15'
versionCode 19
versionName '1.1.1+fork.19'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -46,4 +46,9 @@
-keep class org.joinmastodon.android.AppCenterWrapper { *; }
-keepattributes LineNumberTable
-keepattributes LineNumberTable
# Parceler library
-keep interface org.parceler.Parcel
-keep @org.parceler.Parcel class * { *; }
-keep class **$$Parcelable { *; }

View File

@@ -0,0 +1,39 @@
package org.joinmastodon.android.api;
import android.graphics.Rect;
import android.net.Uri;
import java.io.IOException;
public class AvatarResizedImageRequestBody extends ResizedImageRequestBody{
public AvatarResizedImageRequestBody(Uri uri, ProgressListener progressListener) throws IOException{
super(uri, 0, progressListener);
}
@Override
protected int[] getTargetSize(int srcWidth, int srcHeight){
float factor=400f/Math.min(srcWidth, srcHeight);
return new int[]{Math.round(srcWidth*factor), Math.round(srcHeight*factor)};
}
@Override
protected boolean needResize(int srcWidth, int srcHeight){
return srcHeight>400 || srcWidth!=srcHeight;
}
@Override
protected boolean needCrop(int srcWidth, int srcHeight){
return srcWidth!=srcHeight;
}
@Override
protected Rect getCropBounds(int srcWidth, int srcHeight){
Rect rect=new Rect();
if(srcWidth>srcHeight){
rect.set(srcWidth/2-srcHeight/2, 0, srcWidth/2-srcHeight/2+srcHeight, srcHeight);
}else{
rect.set(0, srcHeight/2-srcWidth/2, srcWidth, srcHeight/2-srcWidth/2+srcWidth);
}
return rect;
}
}

View File

@@ -14,6 +14,7 @@ import android.os.Build;
import android.provider.OpenableColumns;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
import java.io.FileOutputStream;
@@ -30,62 +31,105 @@ public class ResizedImageRequestBody extends CountingRequestBody{
private File tempFile;
private Uri uri;
private String contentType;
private int maxSize;
public ResizedImageRequestBody(Uri uri, int maxSize, ProgressListener progressListener) throws IOException{
super(progressListener);
this.uri=uri;
contentType=MastodonApp.context.getContentResolver().getType(uri);
this.maxSize=maxSize;
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inJustDecodeBounds=true;
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
BitmapFactory.decodeStream(in, null, opts);
if("file".equals(uri.getScheme())){
BitmapFactory.decodeFile(uri.getPath(), opts);
contentType=UiUtils.getFileMediaType(new File(uri.getPath())).type();
}else{
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
BitmapFactory.decodeStream(in, null, opts);
}
contentType=MastodonApp.context.getContentResolver().getType(uri);
}
if(opts.outWidth*opts.outHeight>maxSize){
if(needResize(opts.outWidth, opts.outHeight) || needCrop(opts.outWidth, opts.outHeight)){
Bitmap bitmap;
if(Build.VERSION.SDK_INT>=29){
bitmap=ImageDecoder.decodeBitmap(ImageDecoder.createSource(MastodonApp.context.getContentResolver(), uri), (decoder, info, source)->{
int targetWidth=Math.round((float)Math.sqrt((float)maxSize*((float)info.getSize().getWidth()/info.getSize().getHeight())));
int targetHeight=Math.round((float)Math.sqrt((float)maxSize*((float)info.getSize().getHeight()/info.getSize().getWidth())));
if(Build.VERSION.SDK_INT>=28){
ImageDecoder.Source source;
if("file".equals(uri.getScheme())){
source=ImageDecoder.createSource(new File(uri.getPath()));
}else{
source=ImageDecoder.createSource(MastodonApp.context.getContentResolver(), uri);
}
bitmap=ImageDecoder.decodeBitmap(source, (decoder, info, _source)->{
int[] size=getTargetSize(info.getSize().getWidth(), info.getSize().getHeight());
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
decoder.setTargetSize(targetWidth, targetHeight);
decoder.setTargetSize(size[0], size[1]);
// Breaks images in mysterious ways
// if(needCrop(size[0], size[1]))
// decoder.setCrop(getCropBounds(size[0], size[1]));
});
if(needCrop(bitmap.getWidth(), bitmap.getHeight())){
Rect crop=getCropBounds(bitmap.getWidth(), bitmap.getHeight());
bitmap=Bitmap.createBitmap(bitmap, crop.left, crop.top, crop.width(), crop.height());
}
}else{
int targetWidth=Math.round((float)Math.sqrt((float)maxSize*((float)opts.outWidth/opts.outHeight)));
int targetHeight=Math.round((float)Math.sqrt((float)maxSize*((float)opts.outHeight/opts.outWidth)));
int[] size=getTargetSize(opts.outWidth, opts.outHeight);
int targetWidth=size[0];
int targetHeight=size[1];
float factor=opts.outWidth/(float)targetWidth;
opts=new BitmapFactory.Options();
opts.inSampleSize=(int)factor;
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
bitmap=BitmapFactory.decodeStream(in, null, opts);
if("file".equals(uri.getScheme())){
bitmap=BitmapFactory.decodeFile(uri.getPath(), opts);
}else{
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
bitmap=BitmapFactory.decodeStream(in, null, opts);
}
}
if(factor%1f!=0f){
Bitmap scaled=Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
new Canvas(scaled).drawBitmap(bitmap, null, new Rect(0, 0, targetWidth, targetHeight), new Paint(Paint.FILTER_BITMAP_FLAG));
boolean needCrop=needCrop(targetWidth, targetHeight);
if(factor%1f!=0f || needCrop){
Rect srcBounds=null;
Rect dstBounds;
if(needCrop){
Rect crop=getCropBounds(targetWidth, targetHeight);
dstBounds=new Rect(0, 0, crop.width(), crop.height());
srcBounds=new Rect(
Math.round(crop.left/(float)targetWidth*bitmap.getWidth()),
Math.round(crop.top/(float)targetHeight*bitmap.getHeight()),
Math.round(crop.right/(float)targetWidth*bitmap.getWidth()),
Math.round(crop.bottom/(float)targetHeight*bitmap.getHeight())
);
}else{
dstBounds=new Rect(0, 0, targetWidth, targetHeight);
}
Bitmap scaled=Bitmap.createBitmap(dstBounds.width(), dstBounds.height(), Bitmap.Config.ARGB_8888);
new Canvas(scaled).drawBitmap(bitmap, srcBounds, dstBounds, new Paint(Paint.FILTER_BITMAP_FLAG));
bitmap=scaled;
}
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
int rotation;
int orientation=0;
if("file".equals(uri.getScheme())){
ExifInterface exif=new ExifInterface(uri.getPath());
orientation=exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
}else if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
try(InputStream in=MastodonApp.context.getContentResolver().openInputStream(uri)){
ExifInterface exif=new ExifInterface(in);
int orientation=exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
rotation=switch(orientation){
case ExifInterface.ORIENTATION_ROTATE_90 -> 90;
case ExifInterface.ORIENTATION_ROTATE_180 -> 180;
case ExifInterface.ORIENTATION_ROTATE_270 -> 270;
default -> 0;
};
}
if(rotation!=0){
Matrix matrix=new Matrix();
matrix.setRotate(rotation);
bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
orientation=exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
}
}
int rotation=switch(orientation){
case ExifInterface.ORIENTATION_ROTATE_90 -> 90;
case ExifInterface.ORIENTATION_ROTATE_180 -> 180;
case ExifInterface.ORIENTATION_ROTATE_270 -> 270;
default -> 0;
};
if(rotation!=0){
Matrix matrix=new Matrix();
matrix.setRotate(rotation);
bitmap=Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
}
}
tempFile=new File(MastodonApp.context.getCacheDir(), "tmp_upload_image");
boolean isPNG="image/png".equals(contentType);
tempFile=File.createTempFile("mastodon_tmp_resized", null);
try(FileOutputStream out=new FileOutputStream(tempFile)){
if("image/png".equals(contentType)){
if(isPNG){
bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
}else{
bitmap.compress(Bitmap.CompressFormat.JPEG, 97, out);
@@ -94,9 +138,13 @@ public class ResizedImageRequestBody extends CountingRequestBody{
}
length=tempFile.length();
}else{
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
cursor.moveToFirst();
length=cursor.getInt(0);
if("file".equals(uri.getScheme())){
length=new File(uri.getPath()).length();
}else{
try(Cursor cursor=MastodonApp.context.getContentResolver().query(uri, new String[]{OpenableColumns.SIZE}, null, null, null)){
cursor.moveToFirst();
length=cursor.getInt(0);
}
}
}
}
@@ -125,4 +173,22 @@ public class ResizedImageRequestBody extends CountingRequestBody{
}
}
}
protected int[] getTargetSize(int srcWidth, int srcHeight){
int targetWidth=Math.round((float)Math.sqrt((float)maxSize*((float)srcWidth/srcHeight)));
int targetHeight=Math.round((float)Math.sqrt((float)maxSize*((float)srcHeight/srcWidth)));
return new int[]{targetWidth, targetHeight};
}
protected boolean needResize(int srcWidth, int srcHeight){
return srcWidth*srcHeight>maxSize;
}
protected boolean needCrop(int srcWidth, int srcHeight){
return false;
}
protected Rect getCropBounds(int srcWidth, int srcHeight){
return null;
}
}

View File

@@ -2,13 +2,16 @@ package org.joinmastodon.android.api.requests.accounts;
import android.net.Uri;
import org.joinmastodon.android.api.AvatarResizedImageRequestBody;
import org.joinmastodon.android.api.ContentUriRequestBody;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.ResizedImageRequestBody;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AccountField;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
import okhttp3.MultipartBody;
@@ -39,21 +42,21 @@ public class UpdateAccountCredentials extends MastodonAPIRequest<Account>{
}
@Override
public RequestBody getRequestBody(){
public RequestBody getRequestBody() throws IOException{
MultipartBody.Builder bldr=new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("display_name", displayName)
.addFormDataPart("note", bio);
if(avatar!=null){
bldr.addFormDataPart("avatar", UiUtils.getFileName(avatar), new ContentUriRequestBody(avatar, null));
bldr.addFormDataPart("avatar", UiUtils.getFileName(avatar), new AvatarResizedImageRequestBody(avatar, null));
}else if(avatarFile!=null){
bldr.addFormDataPart("avatar", avatarFile.getName(), RequestBody.create(UiUtils.getFileMediaType(avatarFile), avatarFile));
bldr.addFormDataPart("avatar", avatarFile.getName(), new AvatarResizedImageRequestBody(Uri.fromFile(avatarFile), null));
}
if(cover!=null){
bldr.addFormDataPart("header", UiUtils.getFileName(cover), new ContentUriRequestBody(cover, null));
bldr.addFormDataPart("header", UiUtils.getFileName(cover), new ResizedImageRequestBody(cover, 1500*500, null));
}else if(coverFile!=null){
bldr.addFormDataPart("header", coverFile.getName(), RequestBody.create(UiUtils.getFileMediaType(coverFile), coverFile));
bldr.addFormDataPart("header", coverFile.getName(), new ResizedImageRequestBody(Uri.fromFile(coverFile), 1500*500, null));
}
if(fields.isEmpty()){
bldr.addFormDataPart("fields_attributes[0][name]", "").addFormDataPart("fields_attributes[0][value]", "");

View File

@@ -11,7 +11,7 @@ public class CreateOAuthApp extends MastodonAPIRequest<Application>{
}
private static class Request{
public String clientName="Mastadon for Android";
public String clientName="Mastodon for Android Fork";
public String redirectUris=AccountSessionManager.REDIRECT_URI;
public String scopes=AccountSessionManager.SCOPE;
public String website="https://github.com/sk22/mastodon-android-fork";

View File

@@ -213,6 +213,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
replyTo=Parcels.unwrap(getArguments().getParcelable("replyTo"));
statusVisibility=replyTo.visibility;
}
if(getArguments().containsKey("visibility")){
statusVisibility=(StatusPrivacy) getArguments().getSerializable("visibility");
}
if(savedInstanceState!=null){
statusVisibility=(StatusPrivacy) savedInstanceState.getSerializable("visibility");
}

View File

@@ -137,10 +137,10 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
int id=menuItem.getItemId();
if(id==R.id.delete){
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
}else if(id==R.id.pin || id==R.id.unpin){
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
}else if(id==R.id.delete_and_redraft) {
UiUtils.confirmDeleteAndRedraftPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
}else if(id==R.id.pin || id==R.id.unpin){
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
}else if(id==R.id.mute){
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, relationship!=null && relationship.muting, r->{});
}else if(id==R.id.block){
@@ -254,6 +254,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
Menu menu=optionsMenu.getMenu();
boolean isOwnPost=AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account);
menu.findItem(R.id.delete).setVisible(item.status!=null && isOwnPost);
menu.findItem(R.id.delete_and_redraft).setVisible(item.status!=null && isOwnPost);
menu.findItem(R.id.pin).setVisible(item.status!=null && isOwnPost && !item.status.pinned);
menu.findItem(R.id.unpin).setVisible(item.status!=null && isOwnPost && item.status.pinned);
menu.findItem(R.id.open_in_browser).setVisible(item.status!=null);

View File

@@ -56,6 +56,7 @@ import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.HtmlParser;
@@ -82,6 +83,10 @@ import androidx.annotation.StringRes;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
@@ -415,6 +420,7 @@ public class UiUtils{
args.putBoolean("hasDraft", true);
args.putString("prefilledText", HtmlParser.parse(status.content, status.emojis, status.mentions, status.tags, accountID).toString());
args.putString("spoilerText", status.spoilerText);
args.putSerializable("visibility", status.visibility);
if(status.poll!=null){
args.putInt("pollDuration", (int)status.poll.expiresAt.minus(status.createdAt.getEpochSecond(), ChronoUnit.SECONDS).getEpochSecond());
ArrayList<String> opts=status.poll.options.stream().map(o -> o.title).collect(Collectors.toCollection(ArrayList::new));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mastodon</string>
<string name="get_started">开始使用</string>
<string name="log_in">登录</string>
<string name="next">下一步</string>
@@ -8,12 +7,15 @@
<string name="error">错误</string>
<string name="not_a_mastodon_instance">%s 似乎不是Mastodon实例。</string>
<string name="ok">确定</string>
<string name="preparing_auth">正在跳转...</string>
<string name="finishing_auth">正在完成身份证…</string>
<string name="in_reply_to">回复给 %s</string>
<string name="preparing_auth">正在准备身份认证…</string>
<string name="finishing_auth">正在完成身份证…</string>
<string name="user_boosted">%s 转发了</string>
<string name="in_reply_to">回复 %s</string>
<string name="notifications">通知</string>
<string name="user_followed_you">关注了你</string>
<string name="user_sent_follow_request">发送了关注请求</string>
<string name="user_sent_follow_request">发送了关注请求</string>
<string name="user_favorited">喜欢了你的嘟文</string>
<string name="notification_boosted">转发了你的嘟文</string>
<string name="poll_ended">投票已结束</string>
<string name="time_seconds">%d 秒前</string>
<string name="time_minutes">%d 分钟前</string>
@@ -22,7 +24,7 @@
<string name="share_toot_title">分享</string>
<string name="settings">设置</string>
<string name="publish">发布</string>
<string name="discard_draft">弃草稿?</string>
<string name="discard_draft">弃草稿?</string>
<string name="discard">丢弃</string>
<string name="cancel">取消</string>
<plurals name="followers">
@@ -32,175 +34,302 @@
<item quantity="other">正在关注</item>
</plurals>
<plurals name="posts">
<item quantity="other">帖子</item>
<item quantity="other">嘟文</item>
</plurals>
<string name="posts">帖子</string>
<string name="posts_and_replies">帖子与回复</string>
<string name="media">媒体文件</string>
<string name="posts">嘟文</string>
<string name="posts_and_replies">嘟文和回复</string>
<string name="pinned_posts">置顶</string>
<string name="media">媒体</string>
<string name="profile_about">关于</string>
<string name="button_follow">关注</string>
<string name="button_following">正在关注</string>
<string name="edit_profile">修改个人资料</string>
<string name="mention_user">提及 %s</string>
<string name="share_user">分享 %s</string>
<string name="mute_user">静音 %s</string>
<string name="unmute_user">不再静音 %s</string>
<string name="mute_user">隐藏 %s</string>
<string name="unmute_user">不再隐藏 %s</string>
<string name="block_user">屏蔽 %s</string>
<string name="unblock_user">解除屏蔽 %s</string>
<string name="unblock_user">不再屏蔽 %s</string>
<string name="report_user">举报 %s</string>
<string name="block_domain">屏蔽 %s</string>
<string name="unblock_domain">解除屏蔽 %s</string>
<string name="block_domain">屏蔽 %s 实例</string>
<string name="unblock_domain">不再屏蔽 %s 实例</string>
<plurals name="x_posts">
<item quantity="other">%d 个帖子</item>
<item quantity="other">%d 条嘟文</item>
</plurals>
<string name="profile_joined">加入于</string>
<string name="done">完成</string>
<string name="loading">加载中...</string>
<string name="loading">正在加载…</string>
<string name="field_label">标签</string>
<string name="saving">保存中...</string>
<string name="post_from_user">来自 %s 的帖子</string>
<string name="poll_option_hint">选项 %d</string>
<string name="field_content">内容</string>
<string name="saving">正在保存…</string>
<string name="post_from_user">来自 %s 的嘟文</string>
<string name="poll_option_hint">"选项 %d"</string>
<plurals name="x_minutes">
<item quantity="other">%d分钟</item>
<item quantity="other">%d 分钟</item>
</plurals>
<plurals name="x_hours">
<item quantity="other">%d小时</item>
<item quantity="other">%d 小时</item>
</plurals>
<plurals name="x_days">
<item quantity="other">%d天</item>
<item quantity="other">%d </item>
</plurals>
<string name="compose_poll_duration">时长: %s</string>
<string name="compose_poll_duration">时长%s</string>
<plurals name="x_seconds_left">
<item quantity="other">剩余 %d 秒</item>
<item quantity="other">%d 秒后结束</item>
</plurals>
<plurals name="x_minutes_left">
<item quantity="other">剩余 %d 分钟</item>
<item quantity="other">%d 分钟后结束</item>
</plurals>
<plurals name="x_hours_left">
<item quantity="other">%d 小时后结束</item>
</plurals>
<plurals name="x_days_left">
<item quantity="other">%d 天后结束</item>
</plurals>
<plurals name="x_voters">
<item quantity="other">%d 人已投票</item>
</plurals>
<string name="poll_closed">已关闭</string>
<string name="tap_to_reveal">点击以显示</string>
<string name="confirm_mute_title">隐藏用户</string>
<string name="confirm_mute">确定要隐藏 %s 吗?</string>
<string name="do_mute">确定</string>
<string name="confirm_unmute_title">取消隐藏</string>
<string name="confirm_unmute">确定不再隐藏 %s 吗?</string>
<string name="do_unmute">确定</string>
<string name="confirm_block_title">屏蔽用户</string>
<string name="confirm_block_domain_title">屏蔽域名</string>
<string name="confirm_block">确定要屏蔽 %s 吗?</string>
<string name="do_block">确定</string>
<string name="confirm_unblock_title">解除屏蔽</string>
<string name="confirm_unblock_domain_title">解除屏蔽</string>
<string name="confirm_unblock">确定不再屏蔽 %s 吗?</string>
<string name="do_unblock">确定</string>
<string name="button_muted">已隐藏</string>
<string name="button_blocked">已屏蔽</string>
<string name="action_vote">投票</string>
<string name="tap_to_reveal">点击显示</string>
<string name="delete">删除</string>
<string name="delete_and_redraft">删除以重新编辑</string>
<string name="confirm_delete_title">删除嘟文</string>
<string name="confirm_delete_and_redraft_title">删除嘟文并重新编辑</string>
<string name="confirm_delete">确定要删除这条嘟文吗?</string>
<string name="confirm_delete_and_redraft">确定要删除这条嘟文并重新编辑吗?</string>
<string name="deleting">正在删除…</string>
<string name="pin_post">置顶</string>
<string name="confirm_pin_post_title">置顶嘟文</string>
<string name="confirm_pin_post">确定要在你的资料页置顶这条嘟文吗?</string>
<string name="pinning">正在置顶嘟文…</string>
<string name="unpin_post">取消置顶</string>
<string name="confirm_unpin_post_title">取消嘟文置顶</string>
<string name="confirm_unpin_post">确定不再置顶这条嘟文吗?</string>
<string name="unpinning">正在取消置顶…</string>
<string name="notification_channel_audio_player">音频播放</string>
<string name="play">播放</string>
<string name="pause">暂停</string>
<string name="log_out">登出</string>
<string name="log_out">退出账户</string>
<string name="add_account">添加账户</string>
<string name="search_hint">搜索</string>
<string name="hashtags">话题</string>
<string name="for_you">推荐</string>
<string name="news">新闻</string>
<string name="for_you">推荐关注</string>
<string name="all_notifications">全部</string>
<string name="mentions">提及</string>
<string name="mentions">提及</string>
<plurals name="x_people_talking">
<item quantity="other">%d 人在讨论</item>
</plurals>
<plurals name="discussed_x_times">
<item quantity="other">讨论了 %d 次</item>
</plurals>
<string name="report_title">举报 %s</string>
<string name="report_choose_reason">个帖子有什么问题?</string>
<string name="report_choose_reason">条嘟文有什么问题?</string>
<string name="report_choose_reason_account">%s 有什么问题?</string>
<string name="report_choose_reason_subtitle">选择最匹配</string>
<string name="report_choose_reason_subtitle">选择最匹配的理由</string>
<string name="report_reason_personal">我不喜欢它</string>
<string name="report_reason_personal_subtitle">这不是你想看到的东西</string>
<string name="report_reason_spam">它是垃圾信息</string>
<string name="report_reason_spam_subtitle">恶意链接虚假互动或重复回复</string>
<string name="report_reason_violation">违反了服务器规则</string>
<string name="report_reason_violation_subtitle">知道它会破坏特定规则</string>
<string name="report_reason_spam">垃圾信息</string>
<string name="report_reason_spam_subtitle">恶意链接虚假互动或重复回复</string>
<string name="report_reason_violation">违反社区规则</string>
<string name="report_reason_violation_subtitle">发现它违反了特定规则</string>
<string name="report_reason_other">其他原因</string>
<string name="report_reason_other_subtitle">该问题不符合其他类别</string>
<string name="report_reason_other_subtitle">以上理由都不适用</string>
<string name="report_choose_rule">违反了哪些规则?</string>
<string name="report_choose_rule_subtitle">选择所有适用项</string>
<string name="report_choose_posts">是否有任何帖子支持此报</string>
<string name="report_choose_posts">是否有任何嘟文支持此报?</string>
<string name="report_choose_posts_subtitle">选择所有适用项</string>
<string name="report_comment_title">还有什么你认为我们应该知道的吗?</string>
<string name="report_comment_title">还有什么要告诉我们的吗?</string>
<string name="report_comment_hint">备注</string>
<string name="sending_report">报告发送中...</string>
<string name="report_sent_title">感谢提交举报,我们将会进行处理。</string>
<string name="report_sent_subtitle">我们审查这个问题时,你可以对 %s 采取行动。</string>
<string name="unfollow_user">取消关注 %s</string>
<string name="sending_report">正在提交举报…</string>
<string name="report_sent_title">感谢举报,我们将会处理。</string>
<string name="report_sent_subtitle">我们审查此问题期间,你可以对 %s 采取行动。</string>
<string name="unfollow_user">不再关注 %s</string>
<string name="unfollow">取消关注</string>
<string name="mute_user_explain">你不会在你的主页里看到他们的帖子或重新博客。他们不会知道他们被静音了</string>
<string name="block_user_explain">他们将不再能够关注或看到你的帖子,但他们可以看到他们是否被阻止</string>
<string name="report_personal_title">不想看到这个内容</string>
<string name="report_personal_subtitle">当您在Mastodon看到不喜欢的东西时,您可以从您的体验中移除该人</string>
<string name="mute_user_explain">你不会在你的主页里看到他们的嘟文和转发,他们不会知道你隐藏了他们。</string>
<string name="block_user_explain">他们不能再关注或看到你的嘟文,但他们能知道他们是否被屏蔽</string>
<string name="report_personal_title">不想看到这个?</string>
<string name="report_personal_subtitle">如果在Mastodon看到不喜欢的东西,可以尝试移除它</string>
<string name="back">返回</string>
<string name="instance_catalog_title">Mastodon由来自不同服务器的用户组成。</string>
<string name="instance_catalog_subtitle">根据你的兴趣、地区或其他目的挑选一个服务器。无论你选择哪个服务器,你都可以跟所有人交流。</string>
<string name="search_communities">搜索或输入网址</string>
<string name="instance_rules_title">一些基本规则</string>
<string name="instance_rules_subtitle">请花一分钟来审阅规则设置,并由 %s 管理员执行。</string>
<string name="signup_title">让我们让您在 %s 上设置</string>
<string name="instance_rules_subtitle">请花一分钟来审阅规则,这是由管理员 %s 设置和执行</string>
<string name="signup_title">让我们在 %s 上开始</string>
<string name="edit_photo">编辑</string>
<string name="display_name">昵称</string>
<string name="username">用户名</string>
<string name="email">电子邮箱</string>
<string name="password">密码</string>
<string name="password_note">包括大写字母、特殊字符和数字以增加的密码强度。</string>
<string name="password_note">包括大写字母、特殊字符和数字以增加的密码强度。</string>
<string name="category_academia">学术</string>
<string name="category_activism">行动主义</string>
<string name="category_all">全部</string>
<string name="category_art">艺术</string>
<string name="category_food"></string>
<string name="category_food"></string>
<string name="category_furry">兽迷</string>
<string name="category_games">游戏</string>
<string name="category_general">通用</string>
<string name="category_journalism">新闻</string>
<string name="category_lgbt">性少数</string>
<string name="category_music">音乐</string>
<string name="category_regional">地区</string>
<string name="category_tech">科技</string>
<string name="confirm_email_title">还有一件事</string>
<string name="confirm_email_subtitle">点击我们发送给你的链接来验证你的账户。</string>
<string name="resend">重新发送</string>
<string name="open_email_app">打开电子邮件应用</string>
<string name="resent_email">确认邮件已发送</string>
<string name="resent_email">邮件已发送</string>
<string name="compose_hint">写下你的想法</string>
<string name="content_warning">内容警告</string>
<string name="image_description">图片描述</string>
<string name="add_image_description">添加图片描述…</string>
<string name="retry_upload">重新上传</string>
<string name="image_upload_failed">图片上传失败</string>
<string name="video_upload_failed">视频上传失败</string>
<string name="edit_image">编辑图片</string>
<string name="save">保存</string>
<string name="add_alt_text">添加备注</string>
<string name="alt_text_subtitle">备注可以为视障人士描述你的图片,请尽量只包含足以理解内容的信息。</string>
<string name="alt_text_hint">例如,镜头前有一只狗眯着眼睛警惕地看着四周。</string>
<string name="visibility_public">公开</string>
<string name="visibility_unlisted">不公开</string>
<string name="visibility_followers_only">仅关注者</string>
<string name="visibility_private">我提到的人</string>
<string name="visibility_private">提及的人</string>
<string name="search_all">全部</string>
<string name="search_people">用户</string>
<string name="recent_searches">最近搜索</string>
<string name="step_x_of_n">第 %1$d 步(共 %2$d 步)</string>
<string name="skip">跳过</string>
<string name="notification_type_follow">新关注者</string>
<string name="notification_type_favorite">喜欢</string>
<string name="notification_type_reblog">转发</string>
<string name="notification_type_mention">提及</string>
<string name="notification_type_poll">投票</string>
<string name="choose_account">选择账户</string>
<string name="err_not_logged_in">请先登陆到Mastodon</string>
<plurals name="cant_add_more_than_x_attachments">
<item quantity="other">你不能添加超过 %d 个媒体附件</item>
</plurals>
<string name="media_attachment_unsupported_type">文件 %s 是不支持的类型</string>
<string name="media_attachment_too_big">文件 %1$s 的大小超出了 %2$s MB的限制</string>
<string name="settings_theme">外观</string>
<string name="theme_auto">自动</string>
<string name="theme_light">浅色</string>
<string name="theme_dark">深色</string>
<string name="theme_true_black">纯黑模式</string>
<string name="settings_behavior">行为</string>
<string name="settings_gif">播放动态头像和表情</string>
<string name="settings_custom_tabs">使用内置浏览器</string>
<string name="settings_notifications">通知</string>
<string name="notify_me_when">接收通知的范围</string>
<string name="notify_anyone">任何人</string>
<string name="notify_follower">关注者</string>
<string name="notify_followed">我关注的</string>
<string name="notify_none">没有人</string>
<string name="notify_favorites">喜欢我的帖子</string>
<string name="notify_none">关闭通知</string>
<string name="notify_favorites">喜欢我的嘟文</string>
<string name="notify_follow">关注我</string>
<string name="notify_reblog">转发我的帖子</string>
<string name="notify_reblog">转发我的嘟文</string>
<string name="notify_mention">提及我</string>
<string name="settings_boring">潜水区</string>
<string name="settings_account">户设置</string>
<string name="settings_boring">更多</string>
<string name="settings_account">户设置</string>
<string name="settings_contribute">贡献给Mastodon</string>
<string name="settings_tos">使用条款</string>
<string name="settings_privacy_policy">隐私政策</string>
<string name="settings_spicy">The Spicy Zone</string>
<string name="settings_clear_cache">清除图片缓存</string>
<string name="settings_spicy">危险地带</string>
<string name="settings_clear_cache">清除媒体缓存</string>
<string name="settings_app_version">Mastodon for Android v%1$s (%2$d)</string>
<string name="media_cache_cleared">媒体缓存已清除</string>
<string name="confirm_log_out">确定要退出吗?</string>
<string name="confirm_log_out">确定要退出账户吗?</string>
<string name="sensitive_content">敏感内容</string>
<string name="sensitive_content_explain">作者将此媒体标记为敏感点击显示。</string>
<string name="media_hidden">点击显示</string>
<string name="avatar_description">跳转到 %s 的资料</string>
<string name="sensitive_content_explain">作者将此媒体标记为敏感内容,点击显示。</string>
<string name="media_hidden">点击显示</string>
<string name="avatar_description">跳转到 %s 的资料</string>
<string name="more_options">更多选项</string>
<string name="reveal_content">显示内容</string>
<string name="hide_content">隐藏内容</string>
<string name="new_post">帖子</string>
<string name="new_post">嘟文</string>
<string name="button_reply">回复</string>
<string name="button_reblog">转发</string>
<string name="button_favorite">喜欢</string>
<string name="button_share">分享</string>
<string name="button_bookmark">添加到书签</string>
<string name="bookmarks">书签</string>
<string name="media_no_description">没有描述的媒体</string>
<string name="add_media">添加媒体</string>
<string name="add_poll">添加投票</string>
<string name="add_poll">发起投票</string>
<string name="emoji">表情</string>
<string name="post_visibility">帖子可见性</string>
<string name="post_visibility">嘟文可见性</string>
<string name="home_timeline">主页时间轴</string>
<string name="my_profile">我的资料</string>
<string name="media_viewer">媒体查看器</string>
<string name="follow_user">关注 %s</string>
<string name="unfollowed_user">取消关注 %s</string>
<string name="followed_user">正在关注 %s</string>
<string name="followed_user">正在关注 %s</string>
<string name="open_in_browser">在浏览器中打开</string>
<string name="signup_reason">加入的理由是?</string>
<string name="signup_reason_note">这会有助于我们处理你的申请.</string>
<string name="hide_boosts_from_user">隐藏来自 %s 的转发</string>
<string name="show_boosts_from_user">不再隐藏来自 %s 的转发</string>
<string name="signup_reason">为什么想要加入?</string>
<string name="signup_reason_note">这会帮助我们评估你的申请。</string>
<string name="clear">清除</string>
<string name="profile_header">顶部图片</string>
<string name="profile_picture">个人资料照片</string>
<string name="profile_header">资料页横幅图片</string>
<string name="profile_picture">头像</string>
<string name="reorder">重新排序</string>
<string name="download">下载</string>
<string name="permission_required">需要相应权限</string>
<string name="trending_posts_info_banner">这些是在你的 Mastodon 宇宙中备受关注的内容</string>
<string name="trending_hashtags_info_banner">这些是在你的 Mastodon 宇宙中备受关注的话题</string>
<string name="local_timeline_info_banner">这些是与您使用相同 Mastodon 服务器的人的最新帖子。</string>
<string name="follows_you">关注了你</string>
<string name="current_account">当前账号</string>
<string name="permission_required">需要权限</string>
<string name="storage_permission_to_download">应用程序需要存储权限才能保存文件</string>
<string name="open_settings">打开「设置」</string>
<string name="error_saving_file">保存文件时出错</string>
<string name="file_saved">文件已保存</string>
<string name="downloading">正在下载…</string>
<string name="no_app_to_handle_action">未找到可处理此操作的应用</string>
<string name="local_timeline">本站时间轴</string>
<string name="federated_timeline">联邦时间轴</string>
<string name="trending_posts_info_banner">这是在你的Mastodon角落备受关注的内容。</string>
<string name="trending_hashtags_info_banner">这是在你的Mastodon角落备受关注的话题。</string>
<string name="trending_links_info_banner">这是在你的Mastodon角落分享最多的新闻故事。</string>
<string name="local_timeline_info_banner">这是你所在的Mastodon服务器上的用户发布的最新嘟文。</string>
<string name="federated_timeline_info_banner">这是在你的联邦宇宙中最新发布的嘟文。</string>
<string name="dismiss">驳回</string>
<string name="see_new_posts">查看新嘟文</string>
<string name="load_missing_posts">加载嘟文</string>
<string name="follow_back">关注</string>
<string name="button_follow_pending">已发送关注请求</string>
<string name="follows_you">已关注你</string>
<string name="manually_approves_followers">手动批准关注请求</string>
<string name="current_account">当前账户</string>
<string name="log_out_account">退出 %s</string>
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
<plurals name="x_followers">
<item quantity="other">%,d 名关注者</item>
</plurals>
<plurals name="x_following">
<item quantity="other">关注 %,d 人</item>
</plurals>
<plurals name="x_favorites">
<item quantity="other">%,d 次喜欢</item>
</plurals>
<plurals name="x_reblogs">
<item quantity="other">%,d 次转发</item>
</plurals>
<string name="timestamp_via_app">%1$s 来自 %2$s</string>
<string name="time_now">刚刚</string>
</resources>

View File

@@ -19,17 +19,17 @@
<color name="gray_800t">#CC282C37</color>
<color name="gray_900">#101828</color>
<color name="primary_25">#FAFDFF</color>
<color name="primary_50">#EAF4FB</color>
<color name="primary_100">#D5E9F7</color>
<color name="primary_200">#BFDEF4</color>
<color name="primary_300">#AAD3F0</color>
<color name="primary_400">#95C8EC</color>
<color name="primary_500">#80BCE8</color>
<color name="primary_600">#55A6E1</color>
<color name="primary_700">#2B90D9</color>
<color name="primary_800">#2273AE</color>
<color name="primary_900">#16486D</color>
<color name="primary_25">#fffafd</color>
<color name="primary_50">#fbeaf6</color>
<color name="primary_100">#f7d4ee</color>
<color name="primary_200">#f4bfe7</color>
<color name="primary_300">#f0aade</color>
<color name="primary_400">#ec94d6</color>
<color name="primary_500">#e780cd</color>
<color name="primary_600">#e055bd</color>
<color name="primary_700">#d92aad</color>
<color name="primary_800">#ae218a</color>
<color name="primary_900">#6d1556</color>
<color name="error_25">#FFFBFA</color>
<color name="error_50">#FEF3F2</color>
<color name="error_100">#FEE4E2</color>