Browse Source

close #259, release prep, awaiting crowdin

renovate/org.robolectric-robolectric-4.x
Austin Huang 4 years ago
parent
commit
f908e7b643
No known key found for this signature in database GPG Key ID: 84C23AA04587A91F
  1. 28
      app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java
  2. 10
      app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java
  3. 41
      app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
  4. 43
      app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
  5. 3
      app/src/main/java/awais/instagrabber/repositories/LocationRepository.java
  6. 4
      app/src/main/java/awais/instagrabber/utils/Constants.java
  7. 6
      app/src/main/java/awais/instagrabber/utils/LocaleUtils.java
  8. 2
      app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
  9. 86
      app/src/main/java/awais/instagrabber/webservices/LocationService.java
  10. 2
      app/src/main/res/layout/fragment_hashtag.xml
  11. 323
      app/src/main/res/values-zh/strings.xml
  12. 5
      app/src/main/res/values/arrays.xml
  13. 21
      fastlane/metadata/android/en-US/changelogs/53.txt

28
app/src/main/java/awais/instagrabber/asyncs/HashtagPostFetchService.java

@ -14,7 +14,8 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
private final TagsService tagsService;
private final HashtagModel hashtagModel;
private String nextMaxId;
private boolean moreAvailable, isLoggedIn;
private boolean moreAvailable;
private final boolean isLoggedIn;
public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) {
this.hashtagModel = hashtagModel;
@ -24,7 +25,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback<TagPostsFetchResponse>() {
final ServiceCallback cb = new ServiceCallback<TagPostsFetchResponse>() {
@Override
public void onSuccess(final TagPostsFetchResponse result) {
if (result == null) return;
@ -42,26 +43,9 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
fetchListener.onFailure(t);
}
}
});
else tagsService.fetchGraphQLPosts(hashtagModel.getName().toLowerCase(), nextMaxId, new ServiceCallback<TagPostsFetchResponse>() {
@Override
public void onSuccess(final TagPostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
};
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else tagsService.fetchGraphQLPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
}
@Override

10
app/src/main/java/awais/instagrabber/asyncs/LocationPostFetchService.java

@ -15,15 +15,17 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationModel locationModel;
private String nextMaxId;
private boolean moreAvailable;
private final boolean isLoggedIn;
public LocationPostFetchService(final LocationModel locationModel) {
public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) {
this.locationModel = locationModel;
this.isLoggedIn = isLoggedIn;
locationService = LocationService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
locationService.fetchPosts(locationModel.getId(), nextMaxId, new ServiceCallback<LocationPostsFetchResponse>() {
final ServiceCallback cb = new ServiceCallback<LocationPostsFetchResponse>() {
@Override
public void onSuccess(final LocationPostsFetchResponse result) {
if (result == null) return;
@ -41,7 +43,9 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
fetchListener.onFailure(t);
}
}
});
};
if (isLoggedIn) locationService.fetchPosts(locationModel.getId(), nextMaxId, cb);
else locationService.fetchGraphQLPosts(locationModel.getId(), nextMaxId, cb);
}
@Override

41
app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java

@ -44,6 +44,7 @@ import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.HashtagFetcher;
import awais.instagrabber.asyncs.HashtagPostFetchService;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.databinding.FragmentHashtagBinding;
@ -81,6 +82,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private NestedCoordinatorLayout root;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private boolean opening = false;
private String hashtag;
private HashtagModel hashtagModel;
private ActionMode actionMode;
@ -199,16 +201,37 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
if (opening) return;
else if (TextUtils.isEmpty(feedModel.getProfileModel().getUsername())) {
opening = true;
new PostFetcher(feedModel.getShortCode(), newFeedModel -> {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(newFeedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
opening = false;
}).execute();
}
else {
opening = true;
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
opening = false;
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {

43
app/src/main/java/awais/instagrabber/fragments/LocationFragment.java

@ -47,6 +47,7 @@ import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.LocationPostFetchService;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.NestedCoordinatorLayout;
import awais.instagrabber.databinding.FragmentLocationBinding;
@ -81,6 +82,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private NestedCoordinatorLayout root;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private boolean opening = false;
private String locationId;
private LocationModel locationModel;
private ActionMode actionMode;
@ -197,16 +199,37 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final View profilePicView,
final View mainPostImage,
final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
if (opening) return;
else if (TextUtils.isEmpty(feedModel.getProfileModel().getUsername())) {
opening = true;
new PostFetcher(feedModel.getShortCode(), newFeedModel -> {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(newFeedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
opening = false;
}).execute();
}
else {
opening = true;
final PostViewV2Fragment.Builder builder = PostViewV2Fragment
.builder(feedModel);
if (position >= 0) {
builder.setPosition(position);
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
opening = false;
}
final PostViewV2Fragment fragment = builder
.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage)
.build();
fragment.show(getChildFragmentManager(), "post_view");
}
};
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@ -335,7 +358,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private void setupPosts() {
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new LocationPostFetchService(locationModel))
.setPostFetchService(new LocationPostFetchService(locationModel, isLoggedIn))
.setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_LOCATION_POSTS_LAYOUT)))
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)

3
app/src/main/java/awais/instagrabber/repositories/LocationRepository.java

@ -12,4 +12,7 @@ public interface LocationRepository {
@GET("/api/v1/feed/location/{location}/")
Call<String> fetchPosts(@Path("location") final String locationId,
@QueryMap Map<String, String> queryParams);
@GET("/graphql/query/")
Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams);
}

4
app/src/main/java/awais/instagrabber/utils/Constants.java

@ -58,9 +58,9 @@ public final class Constants {
// spoof
public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 " +
"Instagram 165.1.0.29.119 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 253447818)";
"Instagram 166.1.0.42.245 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 256099205)";
public static final String I_USER_AGENT =
"Instagram 165.1.0.29.119 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 253447818)";
"Instagram 166.1.0.42.245 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 256099205)";
public static final String A_USER_AGENT = "https://Barinsta.AustinHuang.me / mailto:[email protected]";
// see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts
public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" +

6
app/src/main/java/awais/instagrabber/utils/LocaleUtils.java

@ -62,7 +62,7 @@ public final class LocaleUtils {
if (appLanguageIndex == 1) return "en";
if (appLanguageIndex == 2) return "fr";
if (appLanguageIndex == 3) return "es";
if (appLanguageIndex == 4) return "zh";
if (appLanguageIndex == 4) return "zh-rCN";
if (appLanguageIndex == 5) return "in";
if (appLanguageIndex == 6) return "it";
if (appLanguageIndex == 7) return "de";
@ -72,7 +72,9 @@ public final class LocaleUtils {
if (appLanguageIndex == 11) return "fa";
if (appLanguageIndex == 12) return "mk";
if (appLanguageIndex == 13) return "vi";
if (appLanguageIndex == 14) return "hi";
if (appLanguageIndex == 14) return "zh-rTW";
if (appLanguageIndex == 15) return "hi";
if (appLanguageIndex == 16) return "cs";
return null;
}

2
app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java

@ -671,8 +671,6 @@ public final class ResponseBodyUtils {
}
final JSONObject feedItem = itemJson.getJSONObject("node");
final String mediaType = feedItem.optString("__typename");
if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType))
return null;
final boolean isVideo = feedItem.optBoolean("is_video");
final long videoViews = feedItem.optLong("video_view_count", 0);

86
app/src/main/java/awais/instagrabber/webservices/LocationService.java

@ -12,7 +12,9 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.models.FeedModel;
@ -27,7 +29,7 @@ import retrofit2.Retrofit;
public class LocationService extends BaseService {
private static final String TAG = "LocationService";
private final LocationRepository repository;
private final LocationRepository repository, webRepository;
private static LocationService instance;
@ -36,6 +38,10 @@ public class LocationService extends BaseService {
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(LocationRepository.class);
final Retrofit webRetrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
webRepository = webRetrofit.create(LocationRepository.class);
}
public static LocationService getInstance() {
@ -48,7 +54,7 @@ public class LocationService extends BaseService {
public void fetchPosts(@NonNull final String locationId,
final String maxId,
final ServiceCallback<LocationPostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.<String, String>builder();
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
@ -117,6 +123,82 @@ public class LocationService extends BaseService {
return feedModels;
}
public void fetchGraphQLPosts(@NonNull final String locationId,
final String maxId,
final ServiceCallback<LocationPostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "36bd0f2bf5911908de389b8ceaa3be6d");
queryMap.put("variables", "{" +
"\"id\":\"" + locationId + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"" +
"}");
final Call<String> request = webRepository.fetchGraphQLPosts(queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
if (callback == null) {
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final LocationPostsFetchResponse tagPostsFetchResponse = parseGraphQLResponse(body);
callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
private LocationPostsFetchResponse parseGraphQLResponse(@NonNull final String body) throws JSONException {
final JSONObject rootroot = new JSONObject(body);
final JSONObject root = rootroot.getJSONObject("data").getJSONObject("location").getJSONObject("edge_location_to_media");
final boolean moreAvailable = root.getJSONObject("page_info").optBoolean("has_next_page");
final String nextMaxId = root.getJSONObject("page_info").optString("end_cursor");
final int numResults = root.optInt("count");
final String status = rootroot.optString("status");
final JSONArray itemsJson = root.optJSONArray("edges");
final List<FeedModel> items = parseGraphQLItems(itemsJson);
return new LocationPostsFetchResponse(
moreAvailable,
nextMaxId,
numResults,
status,
items
);
}
private List<FeedModel> parseGraphQLItems(final JSONArray items) throws JSONException {
if (items == null) {
return Collections.emptyList();
}
final List<FeedModel> feedModels = new ArrayList<>();
for (int i = 0; i < items.length(); i++) {
final JSONObject itemJson = items.optJSONObject(i);
if (itemJson == null) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return feedModels;
}
public static class LocationPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;

2
app/src/main/res/layout/fragment_hashtag.xml

@ -41,7 +41,7 @@
android:gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat"
app:layout_constraintBottom_toTopOf="@id/btnFollowTag"
app:layout_constraintBottom_toTopOf="@id/fav_chip"
app:layout_constraintStart_toEndOf="@id/mainHashtagImage"
app:layout_constraintTop_toTopOf="@id/mainHashtagImage"
tools:text="35 Posts" />

323
app/src/main/res/values-zh/strings.xml

@ -1,323 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="action_about">关于</string>
<string name="action_dms">私聊</string>
<string name="action_settings">设定</string>
<string name="action_download">下载</string>
<string name="action_search">搜索用户名…</string>
<string name="action_compare">比较</string>
<string name="clipboard_error">复制文字时出错</string>
<string name="clipboard_copied">已复制到剪贴板!</string>
<string name="report">报告</string>
<string name="password">密码 (最多32字符)</string>
<string name="set_password">设置密码 (最多32个字符)</string>
<string name="password_no_max">密码</string>
<string name="ok">好的</string>
<string name="yes"></string>
<string name="cancel">取消</string>
<string name="no"></string>
<string name="confirm">确定</string>
<string name="nav_up">往上</string>
<string name="dont_show_again">不再显示</string>
<string name="selected_folder_label">当前目录</string>
<string name="title_favorites">最爱</string>
<string name="title_discover">发现</string>
<string name="title_comments">留言</string>
<string name="title_notifications">通知</string>
<string name="title_highlight">精彩:%s</string>
<string name="title_user_story">用户快拍</string>
<string name="title_changelog">日志</string>
<string name="bottom_toolbar">下方显示工具栏</string>
<string name="update_check">启动时检查更新</string>
<string name="download_user_folder">下载帖子到用户名文件夹</string>
<string name="autoload_posts">自动加载用户所有帖子</string>
<string name="mark_as_seen_setting">查看快拍后将其标记为已读</string>
<string name="mark_as_seen_setting_summary">快拍作者会知道您已看过</string>
<string name="dm_mark_as_seen_setting">查看私信后将其标记为已读</string>
<string name="dm_mark_as_seen_setting_summary">其他成员会知道你看过了</string>
<string name="activity_setting">启用活动通知</string>
<string name="error_loading_profile">载入主页时出错!\n请尝试再次登录并搜索。</string>
<string name="error_creating_folders">创建下载文件夹时出错</string>
<string name="show_feed">显示用户动态 (只在登录后有效)</string>
<string name="save_to_folder">保存至自定义文件夹</string>
<string name="select_folder">选择文件夹</string>
<string name="theme_settings">主题</string>
<string name="login_settings">仅影响登录用户:</string>
<string name="anonymous_settings">仅影响匿名用户:</string>
<string name="privacy_warning">隐私</string>
<string name="instadp_settings">使用 Instadp 获取高清头像</string>
<string name="storiesig_settings">使用 storiesig 获取快拍和精彩</string>
<string name="stories_viewer_settings">快拍查看工具</string>
<string name="import_export">导入/导出</string>
<string name="select_language">语言</string>
<string name="what_to_do_dialog">怎么办?</string>
<string name="main_posts_count">%s\n帖子</string>
<string name="main_posts_count_inline">%s 个帖子</string>
<string name="main_posts_followers">%s\n粉丝</string>
<string name="main_posts_following">%s\n已关注</string>
<string name="post_viewer_video_post">视频帖</string>
<string name="post_viewer_image_post">图片帖</string>
<string name="post_viewer_autoplay_video">自动播放视频</string>
<string name="post_viewer_muted_autoplay">视频默认静音</string>
<string name="post_viewer_post_from">%s - %s</string>
<string name="post_viewer_download_message" comment="post_viewer_download_message&#10;&#10;[Session] is NOT a variable, please translate it and ignore warnings">注: 当前[帖子] 键将默认下载您说看到的那一张照片,直到按返回键退出本帖为止。</string>
<string name="post_viewer_download_dialog_title">选择要下载的</string>
<string name="post_viewer_download_session" comment="post_viewer_download_session&#10;&#10;[Session] is NOT a variable, please translate it and ignore warnings">当前 [帖子]</string>
<string name="post_viewer_download_current">当前</string>
<string name="post_viewer_download_album">整个图集</string>
<string name="show_stories">显示快拍</string>
<string name="no_more_stories">到底啦!</string>
<string name="be_patient">请耐心等待!</string>
<string name="view_story_post">查看帖子</string>
<string name="view_post">查看帖子</string>
<string name="spotify">Spotify</string>
<string name="stories_notloggedin">匿名用户不能使用此功能!</string>
<string name="vote_story_poll">投票</string>
<string name="votef_story_poll">投票成功!</string>
<string name="voted_story_poll">您已投票过了!</string>
<string name="respond_story">回复</string>
<string name="answer_hint">回答…</string>
<string name="answered_story">回答成功!</string>
<string name="reply_story">回复快拍</string>
<string name="reply_hint">回复…</string>
<string name="story_quiz">测验</string>
<string name="story_quizzed">您已回答过了!</string>
<string name="story_mentions">提及</string>
<string name="priv_acc">私密账户</string>
<string name="priv_acc_confirm">取消关注后,您将无法访问帖子!您确定吗?</string>
<string name="no_acc">你可以通过右下角的 更多-&gt; 账户 来登录,或者您无须登录即可查看公开账户!</string>
<string name="no_acc_logged_in">您可以向左/向右滑动查看探索/订阅,或在下方进行搜索!</string>
<string name="empty_acc">暂未发帖</string>
<string name="empty_list">无此类帖!</string>
<string name="curr_version">目前版本: v%s</string>
<string name="read_more">阅读更多…</string>
<string name="login">登录</string>
<string name="logout">退出</string>
<string name="logout_summary">隐身浏览Instagram</string>
<string name="remove_all_acc">删除所有帐户</string>
<string name="remove_all_acc_warning">这将从应用中移除所有已添加的账户!\n若要移除其中一个账户,长按账户切换对话框中的那个账户即可。\n您想要继续吗?</string>
<string name="send_logs">发送调试日志</string>
<string name="time_settings">日期格式</string>
<string name="project_link">参观项目页面</string>
<string name="telegram_link">加入 Telegram 群组</string>
<string name="matrix_link">加入 Matrix 群组</string>
<string name="liked">已赞</string>
<string name="saved">已保存</string>
<string name="tagged">已标记</string>
<string name="dm_person">消息</string>
<string name="like">赞 (%s)</string>
<string name="like_without_count"></string>
<string name="unlike">取消赞 (%s)</string>
<string name="unlike_without_count">取消点赞</string>
<string name="bookmark">加入收藏</string>
<string name="unbookmark">解除收藏</string>
<string name="follow">关注</string>
<string name="unfollow">脱粉</string>
<string name="favorite_short">加入收藏</string>
<string name="unfavorite_short">取消收藏</string>
<string name="block">拉黑</string>
<string name="unblock">解禁</string>
<string name="restrict">限制</string>
<string name="unrestrict">放开</string>
<string name="map">地图</string>
<string name="dialog_export_btn_export">导出</string>
<string name="dialog_export_btn_import">导入</string>
<string name="dialog_export_logins">导出登录</string>
<string name="dialog_export_accounts">帐户</string>
<string name="dialog_export_settings">设置</string>
<string name="dialog_export_favorites">收藏</string>
<string name="dialog_import_settings">导入设置</string>
<string name="dialog_import_logins">导入登录</string>
<string name="dialog_import_accounts">导入账户</string>
<string name="dialog_import_favorites">导入收藏</string>
<string name="dialog_import_success">成功导入!</string>
<string name="dialog_import_failed">导入失败!</string>
<string name="dialog_export_success">成功导出!</string>
<string name="dialog_export_failed">导出失败!</string>
<string name="dialog_export_err_password_empty">密码为空!</string>
<string name="refresh">刷新</string>
<string name="get_cookies">获取 cookies</string>
<string name="desktop_2fa">桌面版</string>
<string name="time_settings_title_custom">使用自定义格式</string>
<string name="time_settings_title_separator">分界</string>
<string name="time_settings_title_time_format">时间格式</string>
<string name="time_settings_title_date_format">日期格式</string>
<string name="time_settings_title_preview">预览</string>
<string name="time_settings_swap_time">互换时间和日期位置</string>
<string name="quick_access_info_dialog">最爱面板让您添加您最爱的标签和用户。\n\n而快捷通道是用来迅速切换账户的。\n\n注1: 请确保各个账户已登录 [设定 &gt; 登录] 来添加账户!\n\n注2: 登出当前账户之后再登录另一个账户。</string>
<string name="quick_access_cannot_delete_curr">无法删除正在使用的账户</string>
<string name="quick_access_confirm_delete">您确定要删除\"%s\"吗?</string>
<string name="profile_viewer_imageinfo">宽: %d\n高: %d</string>
<string name="profile_viewer_colordepth_prefix">\n色深:</string>
<string name="profile_endpoint">选择高清头像服务\n(不影响标签)</string>
<string name="open_profile">打开主页</string>
<string name="view_pfp">查看头像</string>
<string name="direct_messages_you"></string>
<string name="direct_messages_sent_link">分享了</string>
<string name="direct_messages_sent_media">分享了媒体</string>
<string name="direct_messages_sent_raven">分享了定时消息</string>
<string name="direct_messages_replied_story">回复了快拍</string>
<string name="direct_messages_reacted_story">对快拍留下了心情</string>
<string name="direct_messages_mention_story">在快拍中提到了某人</string>
<string name="dms_inbox_raven_message_unknown"><i>不支持此类消息</i></string>
<string name="dms_inbox_open_link">打开链接</string>
<string name="dms_inbox_copy_text">复制文本</string>
<string name="dms_inbox_download">下载附件</string>
<string name="dms_inbox_like">点赞消息</string>
<string name="dms_inbox_unlike">取消点赞</string>
<string name="dms_inbox_unsend">撤回消息</string>
<string name="dms_inbox_author">查看作者资料</string>
<string name="dms_inbox_media_shared_from">分享了 %s 的帖子</string>
<string name="dms_inbox_raven_media_unknown"><i>媒体格式不明</i></string>
<string name="dms_inbox_raven_media_expired">消息已过期!</string>
<string name="dms_inbox_raven_media_delivered">已送达</string>
<string name="dms_inbox_raven_media_sent">已发送</string>
<string name="dms_inbox_raven_media_opened">已打开</string>
<string name="dms_inbox_raven_media_replayed">已回放</string>
<string name="dms_inbox_raven_media_sending">发送中…</string>
<string name="dms_inbox_raven_media_blocked">已屏蔽</string>
<string name="dms_inbox_raven_media_suggested">为您推荐</string>
<string name="dms_inbox_raven_media_screenshot">已截屏</string>
<string name="dms_inbox_raven_media_cant_deliver">无法发送</string>
<string name="dms_action_success">非常成功!</string>
<string name="dms_action_leave">离开</string>
<string name="dms_action_leave_question">离开此聊天吗?</string>
<string name="dms_action_kick">移除成员</string>
<string name="dms_left_users">已离开成员</string>
<string name="direct_download">直接下载</string>
<string name="direct_download_desc">直接下载至手机!</string>
<string name="direct_download_loading">读取帖子</string>
<string name="direct_download_perms_ask">请给予权限再尝试下载!</string>
<string name="downloader_started">下载开始</string>
<string name="downloader_complete">下载完成</string>
<string name="downloader_downloading_post">帖子下载中…</string>
<string name="downloader_downloading_media">媒体下载中</string>
<string name="downloader_downloading_pfp">大头贴下载中</string>
<string name="downloader_downloaded_in_folder">帖子以下载至下载文件夹!</string>
<string name="downloader_unknown_error">出现不详错误!!!</string>
<string name="downloader_error_creating_folder">创建文件夹时出错!</string>
<string name="downloader_error_download_file">下载文件时出错</string>
<string name="downloader_too_many">您一次只能下载100个帖子。切勿贪得无厌!</string>
<string name="comment_viewer_copy_user">复制用户名</string>
<string name="comment_viewer_copy_comment">复制评论</string>
<string name="comment_viewer_reply_comment">回复评论</string>
<string name="comment_viewer_like_comment">赞评论</string>
<string name="comment_viewer_unlike_comment">取消赞</string>
<string name="comment_viewer_delete_comment">删除评论</string>
<string name="comment_send_empty_comment">评论要写字的!</string>
<string name="comment_view_mention_user_search">要搜索用户名吗?</string>
<string name="comment_view_mention_hash_search">要搜索标签吗?</string>
<string name="comment_view_mention_location_search">要搜索地点吗?</string>
<string name="followers_type_followers">粉丝</string>
<string name="followers_type_following">关注</string>
<string name="followers_compare">比较粉丝和关注</string>
<string name="followers_both_following">互粉</string>
<string name="followers_not_following">未关注 %s</string>
<string name="followers_not_follower">%s 未关注</string>
<string name="login_error_loading_cookies">载入 cookies 时出错</string>
<string name="login_success_loading_cookies">成功载入 cookies!\n若你仍不能查看私密页面/帖子,重新登录!</string>
<string name="comment_hint">撰写一条新评论…</string>
<string name="dm_hint">撰写一条新消息…</string>
<string name="liked_notif">赞了你的帖子</string>
<string name="comment_notif">评论了你的帖子</string>
<string name="follow_notif">关注了您</string>
<string name="mention_notif">提到了您</string>
<string name="tagged_notif">在一个帖子中标记了你</string>
<string name="request_notif">请求关注您</string>
<string name="request_approve">同意请求</string>
<string name="request_reject">拒绝请求</string>
<string name="share_public_post">分享这个公开帖子给…</string>
<string name="share_private_post">这是一个私密帖!分享给那些可以查看他们的人!</string>
<string name="discover_empty">这个类别有点空…</string>
<string name="update_available">检测到有新版本! (%s)</string>
<string name="update_notice">注意:若您下载的是F-Droid版本,那您还得在F-Droid更新!GitHub版本也是如此。</string>
<string name="updated">感谢阁下更新 Barinsta!</string>
<string name="crash_title">应用崩溃了</string>
<string name="crash_descr">糟糕.. 应用崩溃了,不过别担心,你可以向开发者发送错误报告来帮助他修复问题。(:</string>
<string name="use_amoled_dark_theme">使用 AMOLED 夜间主题</string>
<string name="action_notif">动态</string>
<string name="select_picture">选择图片</string>
<string name="uploading">上传中...</string>
<string name="activity_count_prefix">您有:</string>
<string name="activity_count_relationship">%d 位新粉丝</string>
<string name="activity_count_comments">%d 个评论回复</string>
<string name="activity_count_commentlikes">%d 个评论点赞</string>
<string name="activity_count_usertags">%d 个帖子提到了您</string>
<string name="activity_count_likes">%d 个赞</string>
<string name="activity_notloggedin">您在点击此通知前登出了?!</string>
<string name="feed">动态</string>
<string name="profile">用户</string>
<string name="more">更多</string>
<string name="title_dm">私信</string>
<string name="number_selected">已选择 %d 个</string>
<string name="relogin">重新登录</string>
<string name="relogin_summary">如果遇到问题,请刷新您的 cookie</string>
<string name="logout_success">成功退出!</string>
<string name="dm_thread_info">信息</string>
<string name="mark_as_seen">标记为已读</string>
<string name="skip_update_checkbox">在下次更新之前不再显示</string>
<string name="version">版本</string>
<string name="pref_start_screen">初始界面</string>
<string name="pref_category_general">常规设置</string>
<string name="pref_category_theme">主题</string>
<string name="pref_category_downloads">下载</string>
<string name="pref_category_locale">本地化</string>
<string name="account">账户</string>
<string name="account_hint">当前登录不管用?添加同一个账户即可修复。</string>
<string name="add_account">添加帐户</string>
<string name="about_category_license">许可证(仅英文)</string>
<string name="about_documentation">访问我们的网站</string>
<string name="about_documentation_summary">获取支持、谈天说地、结交好友。祝愉快!</string>
<string name="about_repository">在 GitHub 上查看我们的源代码</string>
<string name="about_repository_summary">围观、加星、报错、贡献,(再次)祝愉快!</string>
<string name="about_feedback">通过电子邮件发送反馈</string>
<string name="about_category_3pt">第三方库</string>
<string name="about_category_3pt_summary">本应用使用了以下第三方开源库:</string>
<string name="reminder">提醒</string>
<string name="reminder_summary">请合理利用此工具,并请合法使用下载内容。</string>
<string name="light_white_theme">白色</string>
<string name="dark_black_theme">黑色</string>
<string name="light_theme_settings">浅色主题</string>
<string name="dark_theme_settings">深色主题</string>
<string name="light_barinsta_theme" comment="Yes, this one is Barista (the theme), you can also substitute it with other coffee-related words">咖啡棕</string>
<string name="dark_material_dark_theme">Material 黑</string>
<string name="added_to_favs">已添加至收藏夹</string>
<string name="add_to_favorites">添加到收藏</string>
<string name="accounts">帐户</string>
<string name="hashtags">话题</string>
<string name="locations">地点</string>
<string name="unknown">未知</string>
<string name="removed_from_favs">已从收藏夹中删除</string>
<string name="backup_and_restore">备份 &amp; 还原</string>
<string name="create_backup">创建</string>
<string name="restore_backup">还原</string>
<string name="file_chosen_label">文件:</string>
<string name="enter_password">输入密码</string>
<string name="select_backup_file">选择备份文件(.zaai/.backup)</string>
<string name="apply">应用</string>
<string name="save">保存</string>
<string name="caption">字幕</string>
<string name="player_timeline_desc">视频播放器时间线</string>
<string name="liking">正在赞…</string>
<string name="like_unsuccessful">点赞失败</string>
<string name="unlike_unsuccessful">取消点赞失败</string>
<string name="unliking">正取消点赞…</string>
<string name="controls">操控</string>
<string name="saving">正保存…</string>
<string name="removing">正在删除……</string>
<string name="save_unsuccessful">保存失败</string>
<string name="save_remove_unsuccessful">删除失败</string>
<string name="downloading">下载中...</string>
<string name="downloader_downloading_child">下载项 %d 中的 %d</string>
<string name="delete">删除</string>
<string name="comment">评论</string>
<string name="layout">布局</string>
<string name="opening_post">正在打开帖子...</string>
<string name="share">分享</string>
<plurals name="likes_count">
<item quantity="other">%d 个赞</item>
</plurals>
<plurals name="comments_count">
<item quantity="other">%d 条评论</item>
</plurals>
</resources>

5
app/src/main/res/values/arrays.xml

@ -15,7 +15,8 @@
<item>Persian [Thanks to @farzadx]</item>
<item>Macedonian [Thanks to @snajdovski]</item>
<item>Vietnamese [Thanks to @fouze555]</item>
<item>Chinese Traditional</item>
<item>Chinese Traditional [Thanks to @Still34]</item>
<item>Hindi</item>
</string-array>
<string-array name="languages_values">
<item>0</item>
@ -30,6 +31,8 @@
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
</string-array>
<string-array name="theme_presets">
<item>Auto / Follow System</item>

21
fastlane/metadata/android/en-US/changelogs/53.txt

@ -0,0 +1,21 @@
* Resolve F-Droid compliance issues
* Restore anonymous access to hashtags and locations
* Various bug fixes
Because F-Droid skipped v19.0.0, the previous changelog is also provided here:
## Features
* We're now (officially) Barinsta! New name, new icon!
* Total revamp of UI. Specifically, the profile/hashtag/location, feed, and discover tabs have been redone. Explore our new look!
* You can now pick a feed layout and a theme!
* You can now DM a user from their profile page!
## Improvements
* You can now skip to another user in story feed by clicking the skip buttons
* URLs and emails in descrptions are now tappable
* Downloaded file names now use shortcode instead of post ID
* Support `FELIX_SHARE` DM message type
## Fixes
* Fix file corruption bug
* Fix video autoplay & auto-mute issues
Loading…
Cancel
Save