Merge remote-tracking branch 'megalodon_main/main'
# Conflicts: # README.md # mastodon/build.gradle # mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java # mastodon/src/main/java/org/joinmastodon/android/PushNotificationReceiver.java # mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java # mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java # mastodon/src/main/java/org/joinmastodon/android/model/Status.java # mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ExtendedFooterStatusDisplayItem.java # mastodon/src/main/res/values-it-rIT/strings_sk.xml # metadata/de-DE/changelogs/83.txt # metadata/de-DE/changelogs/94.txt # metadata/en-US/changelogs/94.txt # metadata/es/changelogs/94.txt # metadata/fr/title.txt # metadata/my-MM/title.txt # metadata/my/short_description.txt # metadata/pt-PT/changelogs/59.txt # metadata/pt-PT/changelogs/61.txt # metadata/pt-PT/full_description.txt # metadata/pt-PT/short_description.txt # metadata/pt-PT/title.txt
This commit is contained in:
33
mastodon/proguard-rules.pro
vendored
33
mastodon/proguard-rules.pro
vendored
@@ -53,3 +53,36 @@
|
||||
-keep interface org.parceler.Parcel
|
||||
-keep @org.parceler.Parcel class * { *; }
|
||||
-keep class **$$Parcelable { *; }
|
||||
|
||||
##---------------Begin: proguard configuration for Gson ----------
|
||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
||||
# removes such information by default, so configure it to keep all of it.
|
||||
-keepattributes Signature
|
||||
|
||||
# For using GSON @Expose annotation
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# Gson specific classes
|
||||
-dontwarn sun.misc.**
|
||||
#-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# Application classes that will be serialized/deserialized over Gson
|
||||
-keep class com.google.gson.examples.android.model.** { <fields>; }
|
||||
|
||||
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
|
||||
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
|
||||
-keep class * extends com.google.gson.TypeAdapter
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
# Prevent R8 from leaving Data object members always null
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
|
||||
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
|
||||
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
|
||||
|
||||
##---------------End: proguard configuration for Gson ----------
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
.ifPresent(req ->
|
||||
req.wrapProgress(this, R.string.loading, true, d -> {
|
||||
UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
|
||||
d.setOnDismissListener((ev) -> finish());
|
||||
d.setOnDismissListener((x) -> finish());
|
||||
}));
|
||||
} else {
|
||||
openComposeFragment(accountId);
|
||||
|
||||
@@ -43,7 +43,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean showAltIndicator;
|
||||
public static boolean showNoAltIndicator;
|
||||
public static boolean enablePreReleases;
|
||||
public static boolean prefixRepliesWithRe;
|
||||
public static PrefixRepliesMode prefixReplies;
|
||||
public static boolean bottomEncoding;
|
||||
public static boolean collapseLongPosts;
|
||||
public static boolean spectatorMode;
|
||||
@@ -57,14 +57,12 @@ public class GlobalUserPreferences{
|
||||
public static boolean loadRemoteAccountFollowers;
|
||||
public static boolean mentionRebloggerAutomatically;
|
||||
public static boolean allowRemoteLoading;
|
||||
public static boolean forwardReportDefault;
|
||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||
public static String publishButtonText;
|
||||
public static ThemePreference theme;
|
||||
public static ColorPreference color;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||
public static Map<String, List<String>> recentLanguages;
|
||||
public static Map<String, List<TimelineDefinition>> pinnedTimelines;
|
||||
public static Set<String> accountsWithLocalOnlySupport;
|
||||
@@ -72,6 +70,10 @@ public class GlobalUserPreferences{
|
||||
public static Set<String> accountsWithContentTypesEnabled;
|
||||
public static Map<String, ContentType> accountsDefaultContentTypes;
|
||||
|
||||
private final static Type recentLanguagesType = new TypeToken<Map<String, List<String>>>() {}.getType();
|
||||
private final static Type pinnedTimelinesType = new TypeToken<Map<String, List<TimelineDefinition>>>() {}.getType();
|
||||
private final static Type accountsDefaultContentTypesType = new TypeToken<Map<String, ContentType>>() {}.getType();
|
||||
|
||||
private final static Type recentEmojisType = new TypeToken<Map<String, Integer>>() {}.getType();
|
||||
public static Map<String, Integer> recentEmojis;
|
||||
|
||||
@@ -126,7 +128,7 @@ public class GlobalUserPreferences{
|
||||
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
||||
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
||||
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
||||
prefixRepliesWithRe=prefs.getBoolean("prefixRepliesWithRe", false);
|
||||
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
|
||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
|
||||
spectatorMode=prefs.getBoolean("spectatorMode", false);
|
||||
@@ -153,6 +155,16 @@ public class GlobalUserPreferences{
|
||||
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
|
||||
|
||||
if (prefs.contains("prefixRepliesWithRe")) {
|
||||
prefixReplies = prefs.getBoolean("prefixRepliesWithRe", false)
|
||||
? PrefixRepliesMode.TO_OTHERS : PrefixRepliesMode.NEVER;
|
||||
prefs.edit()
|
||||
.putString("prefixReplies", prefixReplies.name())
|
||||
.remove("prefixRepliesWithRe")
|
||||
.apply();
|
||||
}
|
||||
|
||||
try {
|
||||
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
|
||||
@@ -179,6 +191,8 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("alwaysExpandContentWarnings", alwaysExpandContentWarnings)
|
||||
.putBoolean("disableMarquee", disableMarquee)
|
||||
.putBoolean("disableSwipe", disableSwipe)
|
||||
.putBoolean("enableDeleteNotifications", enableDeleteNotifications)
|
||||
.putBoolean("translateButtonOpenedOnly", translateButtonOpenedOnly)
|
||||
.putBoolean("showDividers", showDividers)
|
||||
.putBoolean("relocatePublishButton", relocatePublishButton)
|
||||
.putBoolean("uniformNotificationIcon", uniformNotificationIcon)
|
||||
@@ -189,7 +203,7 @@ public class GlobalUserPreferences{
|
||||
.putBoolean("showAltIndicator", showAltIndicator)
|
||||
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
||||
.putBoolean("enablePreReleases", enablePreReleases)
|
||||
.putBoolean("prefixRepliesWithRe", prefixRepliesWithRe)
|
||||
.putString("prefixReplies", prefixReplies.name())
|
||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||
.putBoolean("spectatorMode", spectatorMode)
|
||||
.putBoolean("autoHideFab", autoHideFab)
|
||||
@@ -216,6 +230,7 @@ public class GlobalUserPreferences{
|
||||
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||
.putBoolean("forwardReportDefault", forwardReportDefault)
|
||||
.apply();
|
||||
}
|
||||
|
||||
@@ -242,4 +257,10 @@ public class GlobalUserPreferences{
|
||||
THREADS,
|
||||
DISCUSSIONS
|
||||
}
|
||||
|
||||
public enum PrefixRepliesMode {
|
||||
NEVER,
|
||||
ALWAYS,
|
||||
TO_OTHERS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,11 @@ public class PushNotificationReceiver extends BroadcastReceiver{
|
||||
req.language = preferences.postingDefaultLanguage;
|
||||
req.visibility = preferences.postingDefaultVisibility;
|
||||
req.inReplyToId = notification.status.id;
|
||||
if(!notification.status.spoilerText.isEmpty() && GlobalUserPreferences.prefixRepliesWithRe && !notification.status.spoilerText.startsWith("re: ")){
|
||||
|
||||
if (!notification.status.spoilerText.isEmpty() &&
|
||||
(GlobalUserPreferences.prefixReplies == ALWAYS
|
||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(notification.status.account.id)))
|
||||
&& !notification.status.spoilerText.startsWith("re: ")) {
|
||||
req.spoilerText = "re: " + notification.status.spoilerText;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,12 +94,15 @@ public class AnnouncementsFragment extends BaseStatusListFragment<Announcement>
|
||||
@Override
|
||||
public void onSuccess(List<Announcement> result){
|
||||
if (getActivity() == null) return;
|
||||
List<Announcement> unread = result.stream().filter(a -> !a.read).collect(toList());
|
||||
List<Announcement> read = result.stream().filter(a -> a.read).collect(toList());
|
||||
onDataLoaded(unread, true);
|
||||
onDataLoaded(read, false);
|
||||
if (unread.isEmpty()) setResult(true, null);
|
||||
else unreadIDs = unread.stream().map(a -> a.id).collect(toList());
|
||||
|
||||
// get unread items first
|
||||
List<Announcement> data = result.stream().filter(a -> !a.read).collect(toList());
|
||||
if (data.isEmpty()) setResult(true, null);
|
||||
else unreadIDs = data.stream().map(a -> a.id).collect(toList());
|
||||
|
||||
// append read items at the end
|
||||
data.addAll(result.stream().filter(a -> a.read).collect(toList()));
|
||||
onDataLoaded(data, false);
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode.*;
|
||||
import static org.joinmastodon.android.GlobalUserPreferences.recentLanguages;
|
||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.DRAFTS_AFTER_INSTANT;
|
||||
import static org.joinmastodon.android.api.requests.statuses.CreateStatus.getDraftInstant;
|
||||
@@ -806,9 +807,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(!TextUtils.isEmpty(status.spoilerText)){
|
||||
hasSpoiler=true;
|
||||
spoilerEdit.setVisibility(View.VISIBLE);
|
||||
if(GlobalUserPreferences.prefixRepliesWithRe && !status.spoilerText.startsWith("re: ")){
|
||||
if ((GlobalUserPreferences.prefixReplies == ALWAYS
|
||||
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
|
||||
&& !status.spoilerText.startsWith("re: ")) {
|
||||
spoilerEdit.setText("re: " + status.spoilerText);
|
||||
}else{
|
||||
} else {
|
||||
spoilerEdit.setText(status.spoilerText);
|
||||
}
|
||||
spoilerBtn.setSelected(true);
|
||||
|
||||
@@ -255,6 +255,10 @@ public class FollowRequestsListFragment extends RecyclerFragment<FollowRequestsL
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship == null || !relationship.followedBy){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -41,11 +41,6 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDomain() {
|
||||
return super.getDomain() + "/home";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity){
|
||||
super.onAttach(activity);
|
||||
@@ -53,12 +48,13 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
loadData();
|
||||
}
|
||||
|
||||
private boolean typeFilterPredicate(Status s) {
|
||||
return (GlobalUserPreferences.showReplies || s.inReplyToId == null) &&
|
||||
(GlobalUserPreferences.showBoosts || s.reblog == null);
|
||||
}
|
||||
|
||||
private List<Status> filterPosts(List<Status> items) {
|
||||
// This is the function I must use to solve the filters thing for real
|
||||
return items.stream().filter(i ->
|
||||
(GlobalUserPreferences.showReplies || i.inReplyToId == null) &&
|
||||
(GlobalUserPreferences.showBoosts || i.reblog == null)
|
||||
).collect(Collectors.toList());
|
||||
return items.stream().filter(this::typeFilterPredicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,24 +103,24 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
@Override
|
||||
protected void onHidden(){
|
||||
super.onHidden();
|
||||
// if(!data.isEmpty()){
|
||||
// String topPostID=displayItems.get(list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset()).parentID;
|
||||
// if(!topPostID.equals(lastSavedMarkerID)){
|
||||
// lastSavedMarkerID=topPostID;
|
||||
// new SaveMarkers(topPostID, null)
|
||||
// .setCallback(new Callback<>(){
|
||||
// @Override
|
||||
// public void onSuccess(SaveMarkers.Response result){
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onError(ErrorResponse error){
|
||||
// lastSavedMarkerID=null;
|
||||
// }
|
||||
// })
|
||||
// .exec(accountID);
|
||||
// }
|
||||
// }
|
||||
if(!data.isEmpty()){
|
||||
String topPostID=displayItems.get(Math.max(0, list.getChildAdapterPosition(list.getChildAt(0))-getMainAdapterOffset())).parentID;
|
||||
if(!topPostID.equals(lastSavedMarkerID)){
|
||||
lastSavedMarkerID=topPostID;
|
||||
new SaveMarkers(topPostID, null)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(SaveMarkers.Response result){
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error){
|
||||
lastSavedMarkerID=null;
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onStatusCreated(StatusCreatedEvent ev){
|
||||
@@ -238,7 +234,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
if(filterPredicate.test(s)){
|
||||
if(typeFilterPredicate(s) && filterPredicate.test(s)){
|
||||
targetList.addAll(buildDisplayItems(s));
|
||||
insertedPosts.add(s);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.joinmastodon.android.fragments;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.parceler.Parcels;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.grishka.appkit.api.SimpleCallback;
|
||||
|
||||
public class PinnedPostsListFragment extends StatusListFragment{
|
||||
private Account account;
|
||||
|
||||
public PinnedPostsListFragment() {
|
||||
setListLayoutId(R.layout.recycler_fragment_no_refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
super.onCreate(savedInstanceState);
|
||||
account=Parcels.unwrap(getArguments().getParcelable("profileAccount"));
|
||||
setTitle(R.string.posts);
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
new GetAccountStatuses(account.id, null, null, 100, GetAccountStatuses.Filter.PINNED)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
onDataLoaded(result, false);
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.ACCOUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri getWebUri(Uri.Builder base) {
|
||||
return Uri.parse(account.url);
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
private ProgressBarButton actionButton, notifyButton;
|
||||
private ViewPager2 pager;
|
||||
private NestedRecyclerScrollView scrollView;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, pinnedPostsFragment, mediaFragment;
|
||||
private AccountTimelineFragment postsFragment, postsWithRepliesFragment, mediaFragment;
|
||||
private PinnedPostsListFragment pinnedPostsFragment;
|
||||
// private ProfileAboutFragment aboutFragment;
|
||||
private TabLayout tabbar;
|
||||
private SwipeRefreshLayout refreshLayout;
|
||||
@@ -515,8 +516,14 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
if(postsFragment==null){
|
||||
postsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.DEFAULT, true);
|
||||
postsWithRepliesFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.INCLUDE_REPLIES, false);
|
||||
pinnedPostsFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.PINNED, false);
|
||||
mediaFragment=AccountTimelineFragment.newInstance(accountID, account, GetAccountStatuses.Filter.MEDIA, false);
|
||||
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
args.putParcelable("profileAccount", Parcels.wrap(account));
|
||||
args.putBoolean("__is_tab", true);
|
||||
pinnedPostsFragment=new PinnedPostsListFragment();
|
||||
pinnedPostsFragment.setArguments(args);
|
||||
// aboutFragment=new ProfileAboutFragment();
|
||||
setFields(fields);
|
||||
}
|
||||
@@ -675,6 +682,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, account.statusesCount)));
|
||||
|
||||
if (account.followersCount < 0) followersBtn.setVisibility(View.GONE);
|
||||
if (account.followingCount < 0) followingBtn.setVisibility(View.GONE);
|
||||
|
||||
UiUtils.loadCustomEmojiInTextView(name);
|
||||
UiUtils.loadCustomEmojiInTextView(bio);
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.PrefixRepliesMode;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
@@ -221,14 +222,27 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
GlobalUserPreferences.keepOnlyLatestNotification=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, GlobalUserPreferences.prefixRepliesWithRe, i->{
|
||||
GlobalUserPreferences.prefixRepliesWithRe=i.checked;
|
||||
items.add(new ButtonItem(R.string.sk_settings_prefix_reply_cw_with_re, R.drawable.ic_fluent_arrow_reply_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.settings_prefix_reply_mode);
|
||||
popupMenu.setOnMenuItemClickListener(i -> onPrefixRepliesClick(i, b));
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
b.setText(switch(GlobalUserPreferences.prefixReplies){
|
||||
case TO_OTHERS -> R.string.sk_settings_prefix_replies_to_others;
|
||||
case ALWAYS -> R.string.sk_settings_prefix_replies_always;
|
||||
default -> R.string.sk_settings_prefix_replies_never;
|
||||
});
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_confirm_before_reblog, R.drawable.ic_fluent_checkmark_circle_24_regular, GlobalUserPreferences.confirmBeforeReblog, i->{
|
||||
GlobalUserPreferences.confirmBeforeReblog=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_forward_report_default, R.drawable.ic_fluent_arrow_forward_24_regular, GlobalUserPreferences.forwardReportDefault, i->{
|
||||
GlobalUserPreferences.forwardReportDefault=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_allow_remote_loading, R.drawable.ic_fluent_communication_24_regular, GlobalUserPreferences.allowRemoteLoading, i->{
|
||||
GlobalUserPreferences.allowRemoteLoading=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -541,6 +555,22 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onPrefixRepliesClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
PrefixRepliesMode mode = PrefixRepliesMode.NEVER;
|
||||
if (id == R.id.prefix_replies_always) mode = PrefixRepliesMode.ALWAYS;
|
||||
else if (id == R.id.prefix_replies_to_others) mode = PrefixRepliesMode.TO_OTHERS;
|
||||
GlobalUserPreferences.prefixReplies = mode;
|
||||
|
||||
btn.setText(switch(GlobalUserPreferences.prefixReplies){
|
||||
case TO_OTHERS -> R.string.sk_settings_prefix_replies_to_others;
|
||||
case ALWAYS -> R.string.sk_settings_prefix_replies_always;
|
||||
default -> R.string.sk_settings_prefix_replies_never;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onAutoRevealSpoilerClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
|
||||
@@ -557,12 +587,12 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
|
||||
private void onAutoRevealSpoilerChanged(Button b) {
|
||||
if (GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
b.setText(R.string.sk_settings_auto_reveal_always);
|
||||
b.setText(R.string.sk_settings_auto_reveal_anyone);
|
||||
} else {
|
||||
b.setText(switch(GlobalUserPreferences.autoRevealEqualSpoilers){
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_threads;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_discussions;
|
||||
default -> R.string.sk_settings_auto_reveal_never;
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_author;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_anyone;
|
||||
default -> R.string.sk_settings_auto_reveal_nobody;
|
||||
});
|
||||
if (alwaysRevealSpoilersItem.checked != GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
alwaysRevealSpoilersItem.checked = GlobalUserPreferences.alwaysExpandContentWarnings;
|
||||
|
||||
@@ -178,13 +178,29 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
protected void removeStatus(Status status){
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
int index=-1;
|
||||
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(status.id.equals(displayItems.get(i).parentID)){
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if(status.id.equals(item.parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
if (item.parentID.equals(status.inReplyToId)) {
|
||||
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
|
||||
ancestorLastIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// did we find an ancestor that is also the status' neighbor?
|
||||
if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
|
||||
for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
// update ancestor to have no descendant anymore
|
||||
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
|
||||
}
|
||||
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
|
||||
}
|
||||
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
|
||||
@@ -52,7 +52,8 @@ import me.grishka.appkit.utils.V;
|
||||
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
|
||||
protected Status mainStatus, updatedStatus;
|
||||
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
|
||||
protected boolean contextInitiallyRendered;
|
||||
private StatusContext result;
|
||||
protected boolean contextInitiallyRendered, transitionFinished;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -105,13 +106,20 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
footer.hideCounts=true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
|
||||
if(s.id.equals(mainStatus.id)) {
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, getAccountID(), s.getContentStatus()));
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, accountID, s.getContentStatus()));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTransitionFinished() {
|
||||
transitionFinished = true;
|
||||
maybeApplyContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
if (refreshing) loadMainStatus();
|
||||
@@ -119,72 +127,8 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(StatusContext result){
|
||||
if (getContext() == null) return;
|
||||
Map<String, Status> oldData = null;
|
||||
if(refreshing){
|
||||
oldData = new HashMap<>(data.size());
|
||||
for (Status s : data) oldData.put(s.id, s);
|
||||
data.clear();
|
||||
ancestryMap.clear();
|
||||
displayItems.clear();
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
}
|
||||
|
||||
// TODO: figure out how this code works
|
||||
if(isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
|
||||
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||
ancestryMap.put(i.status.id, i);
|
||||
}
|
||||
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
int prevCount=displayItems.size();
|
||||
onAppendItems(result.descendants);
|
||||
int count=displayItems.size();
|
||||
if(!refreshing)
|
||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||
displayItems.remove(prependedCount);
|
||||
adapter.notifyItemRemoved(prependedCount);
|
||||
count--;
|
||||
}
|
||||
|
||||
for (Status s : data) {
|
||||
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||
// restore previous spoiler/filter revealed states when refreshing
|
||||
if (oldStatus != null) {
|
||||
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||
s.filterRevealed = oldStatus.filterRevealed;
|
||||
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||
s.spoilerText != null &&
|
||||
s.spoilerText.equals(mainStatus.spoilerText) &&
|
||||
mainStatus.spoilerRevealed) {
|
||||
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||
s.spoilerRevealed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataLoaded();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
list.scrollToPosition(displayItems.size()-count);
|
||||
|
||||
// no animation is going to happen, so proceeding to apply right now
|
||||
if (data.size() == 1) {
|
||||
contextInitiallyRendered = true;
|
||||
// for the case that the main status has already finished loading
|
||||
maybeApplyMainStatus();
|
||||
}
|
||||
ThreadFragment.this.result = result;
|
||||
maybeApplyContext();
|
||||
}
|
||||
})
|
||||
.exec(accountID);
|
||||
@@ -207,6 +151,77 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
protected void maybeApplyContext() {
|
||||
if (!transitionFinished || result == null || getContext() == null) return;
|
||||
Map<String, Status> oldData = null;
|
||||
if(refreshing){
|
||||
oldData = new HashMap<>(data.size());
|
||||
for (Status s : data) oldData.put(s.id, s);
|
||||
data.clear();
|
||||
ancestryMap.clear();
|
||||
displayItems.clear();
|
||||
data.add(mainStatus);
|
||||
onAppendItems(Collections.singletonList(mainStatus));
|
||||
}
|
||||
|
||||
// TODO: figure out how this code works
|
||||
if (isInstanceAkkoma()) sortStatusContext(mainStatus, result);
|
||||
|
||||
result.descendants=filterStatuses(result.descendants);
|
||||
result.ancestors=filterStatuses(result.ancestors);
|
||||
|
||||
for (NeighborAncestryInfo i : mapNeighborhoodAncestry(mainStatus, result)) {
|
||||
ancestryMap.put(i.status.id, i);
|
||||
}
|
||||
|
||||
if(footerProgress!=null)
|
||||
footerProgress.setVisibility(View.GONE);
|
||||
data.addAll(result.descendants);
|
||||
int prevCount=displayItems.size();
|
||||
onAppendItems(result.descendants);
|
||||
int count=displayItems.size();
|
||||
if(!refreshing)
|
||||
adapter.notifyItemRangeInserted(prevCount, count-prevCount);
|
||||
int prependedCount = prependItems(result.ancestors, !refreshing);
|
||||
if (prependedCount > 0 && displayItems.get(prependedCount) instanceof ReblogOrReplyLineStatusDisplayItem) {
|
||||
displayItems.remove(prependedCount);
|
||||
adapter.notifyItemRemoved(prependedCount);
|
||||
count--;
|
||||
}
|
||||
|
||||
for (Status s : data) {
|
||||
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||
// restore previous spoiler/filter revealed states when refreshing
|
||||
if (oldStatus != null) {
|
||||
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||
s.filterRevealed = oldStatus.filterRevealed;
|
||||
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||
s.spoilerText != null &&
|
||||
s.spoilerText.equals(mainStatus.spoilerText) &&
|
||||
mainStatus.spoilerRevealed) {
|
||||
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||
s.spoilerRevealed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataLoaded();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
list.scrollToPosition(displayItems.size()-count);
|
||||
|
||||
// no animation is going to happen, so proceeding to apply right now
|
||||
if (data.size() == 1) {
|
||||
contextInitiallyRendered = true;
|
||||
// for the case that the main status has already finished loading
|
||||
maybeApplyMainStatus();
|
||||
}
|
||||
|
||||
result = null;
|
||||
}
|
||||
|
||||
protected Object maybeApplyMainStatus() {
|
||||
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
||||
|
||||
@@ -337,10 +352,60 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
}
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
||||
data.add(ev.status);
|
||||
onAppendItems(Collections.singletonList(ev.status));
|
||||
if (ev.status.inReplyToId == null) return;
|
||||
Status repliedToStatus = getStatusByID(ev.status.inReplyToId);
|
||||
if (repliedToStatus == null) return;
|
||||
NeighborAncestryInfo ancestry = ancestryMap.get(repliedToStatus.id);
|
||||
|
||||
int nextDisplayItemsIndex = -1, indexOfPreviousDisplayItem = -1;
|
||||
|
||||
for (int i = 0; i < displayItems.size(); i++) {
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if (repliedToStatus.id.equals(item.parentID)) {
|
||||
// saving the replied-to status' display items index to eventually reach the last one
|
||||
indexOfPreviousDisplayItem = i;
|
||||
item.hasDescendantNeighbor = true;
|
||||
} else if (indexOfPreviousDisplayItem >= 0 && nextDisplayItemsIndex == -1) {
|
||||
// previous display item was the replied-to status' display items
|
||||
nextDisplayItemsIndex = i;
|
||||
// nothing left to do if there's no other reply to that status
|
||||
if (ancestry.descendantNeighbor == null) break;
|
||||
}
|
||||
if (ancestry.descendantNeighbor != null && item.parentID.equals(ancestry.descendantNeighbor.id)) {
|
||||
// existing reply shall no longer have the replied-to status as its neighbor
|
||||
item.hasAncestoringNeighbor = false;
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to inserting the item at the end
|
||||
nextDisplayItemsIndex = nextDisplayItemsIndex >= 0 ? nextDisplayItemsIndex : displayItems.size();
|
||||
int nextDataIndex = data.indexOf(repliedToStatus) + 1;
|
||||
|
||||
// if replied-to status already has another reply...
|
||||
if (ancestry.descendantNeighbor != null) {
|
||||
// update the reply's ancestry to remove its ancestoring neighbor (as we did above)
|
||||
ancestryMap.get(ancestry.descendantNeighbor.id).ancestoringNeighbor = null;
|
||||
// make sure the existing reply has a reply line
|
||||
if (nextDataIndex < data.size() &&
|
||||
!(displayItems.get(nextDisplayItemsIndex) instanceof ReblogOrReplyLineStatusDisplayItem)) {
|
||||
Status nextStatus = data.get(nextDataIndex);
|
||||
if (!nextStatus.account.id.equals(repliedToStatus.account.id)) {
|
||||
// create reply line manually since we're not building that status' items
|
||||
displayItems.add(nextDisplayItemsIndex, StatusDisplayItem.buildReplyLine(
|
||||
this, nextStatus, accountID, nextStatus, repliedToStatus.account, false
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update replied-to status' ancestry
|
||||
ancestry.descendantNeighbor = ev.status;
|
||||
|
||||
// add ancestry for newly created status before building its display items
|
||||
ancestryMap.put(ev.status.id, new NeighborAncestryInfo(ev.status, null, repliedToStatus));
|
||||
displayItems.addAll(nextDisplayItemsIndex, buildDisplayItems(ev.status));
|
||||
data.add(nextDataIndex, ev.status);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -244,6 +244,10 @@ public class DiscoverAccountsFragment extends RecyclerFragment<DiscoverAccountsF
|
||||
followersLabel.setText(getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
relationship=relationships.get(item.account.id);
|
||||
if(relationship==null){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -15,6 +15,7 @@ import android.widget.TextView;
|
||||
import com.squareup.otto.Subscribe;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.reports.SendReport;
|
||||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||
@@ -39,7 +40,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
private TextView forwardReportText;
|
||||
private Switch forwardReportSwitch;
|
||||
private EditText commentEdit;
|
||||
private boolean forwardReport;
|
||||
private boolean forwardReport = GlobalUserPreferences.forwardReportDefault;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState){
|
||||
@@ -89,7 +90,7 @@ public class ReportCommentFragment extends MastodonToolbarFragment{
|
||||
} else {
|
||||
forwardReportItem.setOnClickListener(this::onForwardReportClick);
|
||||
forwardReportText.setText(getActivity().getString(R.string.sk_forward_report_to, domain));
|
||||
forwardReportSwitch.setChecked(forwardReport = true);
|
||||
forwardReportSwitch.setChecked(forwardReport);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public abstract class BaseModel implements Cloneable{
|
||||
/**
|
||||
* indicates the profile has been fetched from a foreign instance.
|
||||
*
|
||||
* @see MastodonAPIRequest#execRemote
|
||||
* @see org.joinmastodon.android.api.MastodonAPIRequest#execRemote
|
||||
*/
|
||||
public transient boolean isRemote;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
public Instant createdAt;
|
||||
@RequiredField
|
||||
public Account account;
|
||||
// @RequiredField
|
||||
// @RequiredField
|
||||
public String content;
|
||||
@RequiredField
|
||||
public StatusPrivacy visibility;
|
||||
@@ -178,9 +178,10 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
return strippedText;
|
||||
}
|
||||
|
||||
public boolean canBeBoosted(String accountID){
|
||||
return (visibility==StatusPrivacy.PUBLIC || visibility==StatusPrivacy.UNLISTED || visibility==StatusPrivacy.LOCAL
|
||||
|| (visibility==StatusPrivacy.PRIVATE && account.id.equals(AccountSessionManager.getInstance().getAccount(accountID).self.id)));
|
||||
public boolean isReblogPermitted(String accountID){
|
||||
return visibility.isReblogPermitted(account.id.equals(
|
||||
AccountSessionManager.getInstance().getAccount(accountID).self.id
|
||||
));
|
||||
}
|
||||
|
||||
public static Status ofFake(String id, String text, Instant createdAt) {
|
||||
|
||||
@@ -14,7 +14,7 @@ public enum StatusPrivacy{
|
||||
@SerializedName("local")
|
||||
LOCAL(4); // akkoma
|
||||
|
||||
private int privacy;
|
||||
private final int privacy;
|
||||
|
||||
StatusPrivacy(int privacy) {
|
||||
this.privacy = privacy;
|
||||
@@ -24,6 +24,13 @@ public enum StatusPrivacy{
|
||||
return privacy > other.getPrivacy();
|
||||
}
|
||||
|
||||
public boolean isReblogPermitted(boolean isOwnStatus){
|
||||
return (this == StatusPrivacy.PUBLIC ||
|
||||
this == StatusPrivacy.UNLISTED ||
|
||||
this == StatusPrivacy.LOCAL ||
|
||||
(this == StatusPrivacy.PRIVATE && isOwnStatus));
|
||||
}
|
||||
|
||||
public int getPrivacy() {
|
||||
return privacy;
|
||||
}
|
||||
|
||||
@@ -10,153 +10,170 @@ import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class PhotoLayoutHelper{
|
||||
public static final int MAX_WIDTH=1000;
|
||||
public static final int MAX_HEIGHT=1910;
|
||||
public static final int MAX_HEIGHT=1700;
|
||||
public static final float GAP=1.5f;
|
||||
|
||||
// 2 * margin + close button height - gap. i don't know if the gap subtraction is correct
|
||||
public static final int MIN_HEIGHT = Math.round(V.dp(2 * 12) + V.dp(40) - GAP);
|
||||
|
||||
@NonNull
|
||||
public static TiledLayoutResult processThumbs(List<Attachment> thumbs){
|
||||
int _maxW=MAX_WIDTH;
|
||||
int _maxH=MAX_HEIGHT;
|
||||
float maxRatio=MAX_WIDTH/(float)MAX_HEIGHT;
|
||||
|
||||
TiledLayoutResult result=new TiledLayoutResult();
|
||||
if(thumbs.size()==1){
|
||||
Attachment att=thumbs.get(0);
|
||||
result.rowSizes=result.columnSizes=new int[]{1};
|
||||
if(att.getWidth()>att.getHeight()){
|
||||
result.width=_maxW;
|
||||
result.height=Math.round(att.getHeight()/(float)att.getWidth()*_maxW);
|
||||
float ratio=att.getWidth()/(float) att.getHeight();
|
||||
if(ratio>maxRatio){
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.max(MIN_HEIGHT, Math.round(att.getHeight()/(float)att.getWidth()*MAX_WIDTH));
|
||||
}else{
|
||||
result.height=_maxH;
|
||||
result.width=Math.round(att.getWidth()/(float)att.getHeight()*_maxH);
|
||||
result.height=MAX_HEIGHT;
|
||||
result.width=MAX_WIDTH;//Math.round(att.getWidth()/(float)att.getHeight()*MAX_HEIGHT);
|
||||
}
|
||||
result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, result.width, result.height, 0, 0)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{new TiledLayoutResult.Tile(1, 1, 0, 0)};
|
||||
return result;
|
||||
}else if(thumbs.size()==0){
|
||||
throw new IllegalArgumentException("Empty thumbs array");
|
||||
}
|
||||
|
||||
String orients="";
|
||||
ArrayList<Float> ratios=new ArrayList<Float>();
|
||||
ArrayList<Float> ratios=new ArrayList<>();
|
||||
int cnt=thumbs.size();
|
||||
|
||||
boolean allAreWide=true, allAreSquare=true;
|
||||
|
||||
for(Attachment thumb : thumbs){
|
||||
// float ratio=thumb.isSizeKnown() ? thumb.getWidth()/(float) thumb.getHeight() : 1f;
|
||||
float ratio=thumb.getWidth()/(float) thumb.getHeight();
|
||||
char orient=ratio>1.2 ? 'w' : (ratio<0.8 ? 'n' : 'q');
|
||||
orients+=orient;
|
||||
float ratio=Math.max(0.45f, thumb.getWidth()/(float) thumb.getHeight());
|
||||
if(ratio<=1.2f){
|
||||
allAreWide=false;
|
||||
if(ratio<0.8f)
|
||||
allAreSquare=false;
|
||||
}else{
|
||||
allAreSquare=false;
|
||||
}
|
||||
ratios.add(ratio);
|
||||
}
|
||||
|
||||
float avgRatio=!ratios.isEmpty() ? sum(ratios)/ratios.size() : 1.0f;
|
||||
|
||||
float maxW, maxH, marginW=0, marginH=0;
|
||||
maxW=_maxW;
|
||||
maxH=_maxH;
|
||||
|
||||
float maxRatio=maxW/maxH;
|
||||
|
||||
if(cnt==2){
|
||||
if(orients.equals("ww") && avgRatio>1.4*maxRatio && (ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other
|
||||
float h=Math.min(maxW/ratios.get(0), Math.min(maxW/ratios.get(1), (maxH-marginH)/2.0f));
|
||||
if(allAreWide && avgRatio>1.4*maxRatio && (ratios.get(1)-ratios.get(0))<0.2){ // two wide photos, one above the other
|
||||
float h=Math.max(Math.min(MAX_WIDTH/ratios.get(0), Math.min(MAX_WIDTH/ratios.get(1), (MAX_HEIGHT-GAP)/2.0f)), MIN_HEIGHT/2f);
|
||||
|
||||
result.width=Math.round(maxW);
|
||||
result.height=Math.round(h*2+marginH);
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(h*2+GAP);
|
||||
result.columnSizes=new int[]{result.width};
|
||||
result.rowSizes=new int[]{Math.round(h), Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, maxW, h, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, maxW, h, 0, 1)
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1)
|
||||
};
|
||||
}else if(orients.equals("ww") || orients.equals("qq")){ // next to each other, same ratio
|
||||
float w=((maxW-marginW)/2);
|
||||
float h=Math.min(w/ratios.get(0), Math.min(w/ratios.get(1), maxH));
|
||||
}else if(allAreWide || allAreSquare){ // next to each other, same ratio
|
||||
float w=((MAX_WIDTH-GAP)/2);
|
||||
float h=Math.max(Math.min(w/ratios.get(0), Math.min(w/ratios.get(1), MAX_HEIGHT)), MIN_HEIGHT);
|
||||
|
||||
result.width=Math.round(maxW);
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(h);
|
||||
result.columnSizes=new int[]{Math.round(w), _maxW-Math.round(w)};
|
||||
result.columnSizes=new int[]{Math.round(w), MAX_WIDTH-Math.round(w)};
|
||||
result.rowSizes=new int[]{Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, w, h, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h, 1, 0)
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0)
|
||||
};
|
||||
}else{ // next to each other, different ratios
|
||||
float w0=((maxW-marginW)/ratios.get(1)/(1/ratios.get(0)+1/ratios.get(1)));
|
||||
float w1=(maxW-w0-marginW);
|
||||
float h=Math.min(maxH, Math.min(w0/ratios.get(0), w1/ratios.get(1)));
|
||||
float w0=((MAX_WIDTH-GAP)/ratios.get(1)/(1/ratios.get(0)+1/ratios.get(1)));
|
||||
float w1=(MAX_WIDTH-w0-GAP);
|
||||
float h=Math.max(Math.min(MAX_HEIGHT, Math.min(w0/ratios.get(0), w1/ratios.get(1))), MIN_HEIGHT);
|
||||
|
||||
result.columnSizes=new int[]{Math.round(w0), Math.round(w1)};
|
||||
result.rowSizes=new int[]{Math.round(h)};
|
||||
result.width=Math.round(w0+w1+marginW);
|
||||
result.width=Math.round(w0+w1+GAP);
|
||||
result.height=Math.round(h);
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 1, w0, h, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w1, h, 1, 0)
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0)
|
||||
};
|
||||
}
|
||||
}else if(cnt==3){
|
||||
if(/*(ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) &&*/ orients.equals("www") || true){ // 2nd and 3rd photos are on the next line
|
||||
float hCover=Math.min(maxW/ratios.get(0), (maxH-marginH)*0.66f);
|
||||
float w2=((maxW-marginW)/2);
|
||||
float h=Math.min(maxH-hCover-marginH, Math.min(w2/ratios.get(1), w2/ratios.get(2)));
|
||||
result.width=Math.round(maxW);
|
||||
result.height=Math.round(hCover+h+marginH);
|
||||
result.columnSizes=new int[]{Math.round(w2), _maxW-Math.round(w2)};
|
||||
if((ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) || allAreWide){ // 2nd and 3rd photos are on the next line
|
||||
float hCover=Math.min(MAX_WIDTH/ratios.get(0), (MAX_HEIGHT-GAP)*0.66f);
|
||||
float w2=((MAX_WIDTH-GAP)/2);
|
||||
float h=Math.min(MAX_HEIGHT-hCover-GAP, Math.min(w2/ratios.get(1), w2/ratios.get(2)));
|
||||
if(hCover+h<MIN_HEIGHT){
|
||||
float prevTotalHeight=hCover+h;
|
||||
hCover=MIN_HEIGHT*(hCover/prevTotalHeight);
|
||||
h=MIN_HEIGHT*(h/prevTotalHeight);
|
||||
}
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(hCover+h+GAP);
|
||||
result.columnSizes=new int[]{Math.round(w2), MAX_WIDTH-Math.round(w2)};
|
||||
result.rowSizes=new int[]{Math.round(hCover), Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(2, 1, maxW, hCover, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w2, h, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w2, h, 1, 1)
|
||||
new TiledLayoutResult.Tile(2, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1)
|
||||
};
|
||||
}else{ // 2nd and 3rd photos are on the right part
|
||||
float wCover=Math.min(maxH*ratios.get(0), (maxW-marginW)*0.75f);
|
||||
float h1=(ratios.get(1)*(maxH-marginH)/(ratios.get(2)+ratios.get(1)));
|
||||
float h0=(maxH-h1-marginH);
|
||||
float w=Math.min(maxW-wCover-marginW, Math.min(h1*ratios.get(2), h0*ratios.get(1)));
|
||||
result.width=Math.round(wCover+w+marginW);
|
||||
result.height=Math.round(maxH);
|
||||
float height=Math.min(MAX_HEIGHT, MAX_WIDTH*0.66f/avgRatio);
|
||||
float wCover=Math.min(height*ratios.get(0), (MAX_WIDTH-GAP)*0.66f);
|
||||
float h1=(ratios.get(1)*(height-GAP)/(ratios.get(2)+ratios.get(1)));
|
||||
float h0=(height-h1-GAP);
|
||||
float w=Math.min(MAX_WIDTH-wCover-GAP, Math.min(h1*ratios.get(2), h0*ratios.get(1)));
|
||||
result.width=Math.round(wCover+w+GAP);
|
||||
result.height=Math.round(height);
|
||||
result.columnSizes=new int[]{Math.round(wCover), Math.round(w)};
|
||||
result.rowSizes=new int[]{Math.round(h0), Math.round(h1)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 2, wCover, maxH, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h0, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h1, 1, 1)
|
||||
new TiledLayoutResult.Tile(1, 2, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1)
|
||||
};
|
||||
}
|
||||
}else if(cnt==4){
|
||||
if(/*(ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) &&*/ orients.equals("wwww") || true /* temporary fix */){ // 2nd, 3rd and 4th photos are on the next line
|
||||
float hCover=Math.min(maxW/ratios.get(0), (maxH-marginH)*0.66f);
|
||||
float h=(maxW-2*marginW)/(ratios.get(1)+ratios.get(2)+ratios.get(3));
|
||||
if((ratios.get(0) > 1.2 * maxRatio || avgRatio > 1.5 * maxRatio) || allAreWide){ // 2nd, 3rd and 4th photos are on the next line
|
||||
float hCover=Math.min(MAX_WIDTH/ratios.get(0), (MAX_HEIGHT-GAP)*0.66f);
|
||||
float h=(MAX_WIDTH-2*GAP)/(ratios.get(1)+ratios.get(2)+ratios.get(3));
|
||||
float w0=h*ratios.get(1);
|
||||
float w1=h*ratios.get(2);
|
||||
float w2=h*ratios.get(3);
|
||||
h=Math.min(maxH-hCover-marginH, h);
|
||||
result.width=Math.round(maxW);
|
||||
result.height=Math.round(hCover+h+marginH);
|
||||
result.columnSizes=new int[]{Math.round(w0), Math.round(w1), _maxW-Math.round(w0)-Math.round(w1)};
|
||||
h=Math.min(MAX_HEIGHT-hCover-GAP, h);
|
||||
if(hCover+h<MIN_HEIGHT){
|
||||
float prevTotalHeight=hCover+h;
|
||||
hCover=MIN_HEIGHT*(hCover/prevTotalHeight);
|
||||
h=MIN_HEIGHT*(h/prevTotalHeight);
|
||||
}
|
||||
result.width=MAX_WIDTH;
|
||||
result.height=Math.round(hCover+h+GAP);
|
||||
result.columnSizes=new int[]{Math.round(w0), Math.round(w1), MAX_WIDTH-Math.round(w0)-Math.round(w1)};
|
||||
result.rowSizes=new int[]{Math.round(hCover), Math.round(h)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(3, 1, maxW, hCover, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w0, h, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w1, h, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w2, h, 2, 1),
|
||||
new TiledLayoutResult.Tile(3, 1, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 0, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 2, 1),
|
||||
};
|
||||
}else{ // 2nd, 3rd and 4th photos are on the right part
|
||||
float wCover= Math.min(maxH*ratios.get(0), (maxW-marginW)*0.66f);
|
||||
float w=(maxH-2*marginH)/(1/ratios.get(1)+1/ratios.get(2)+1/ratios.get(3));
|
||||
float height=Math.min(MAX_HEIGHT, MAX_WIDTH*0.66f/avgRatio);
|
||||
float wCover= Math.min(height*ratios.get(0), (MAX_WIDTH-GAP)*0.66f);
|
||||
float w=(height-2*GAP)/(1/ratios.get(1)+1/ratios.get(2)+1/ratios.get(3));
|
||||
float h0=w/ratios.get(1);
|
||||
float h1=w/ratios.get(2);
|
||||
float h2=w/ratios.get(3)+marginH;
|
||||
w=Math.min(maxW-wCover-marginW, w);
|
||||
result.width=Math.round(wCover+marginW+w);
|
||||
result.height=Math.round(maxH);
|
||||
float h2=w/ratios.get(3)+GAP;
|
||||
w=Math.min(MAX_WIDTH-wCover-GAP, w);
|
||||
result.width=Math.round(wCover+GAP+w);
|
||||
result.height=Math.round(height);
|
||||
result.columnSizes=new int[]{Math.round(wCover), Math.round(w)};
|
||||
result.rowSizes=new int[]{Math.round(h0), Math.round(h1), Math.round(h2)};
|
||||
result.tiles=new TiledLayoutResult.Tile[]{
|
||||
new TiledLayoutResult.Tile(1, 3, wCover, maxH, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h0, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h1, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, w, h2, 1, 2),
|
||||
new TiledLayoutResult.Tile(1, 3, 0, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 0),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 1),
|
||||
new TiledLayoutResult.Tile(1, 1, 1, 2),
|
||||
};
|
||||
}
|
||||
}else{
|
||||
@@ -174,14 +191,14 @@ public class PhotoLayoutHelper{
|
||||
HashMap<int[], float[]> tries=new HashMap<>();
|
||||
|
||||
// One line
|
||||
int firstLine, secondLine, thirdLine;
|
||||
tries.put(new int[]{firstLine=cnt}, new float[]{calculateMultiThumbsHeight(ratiosCropped, maxW, marginW)});
|
||||
int firstLine, secondLine;
|
||||
tries.put(new int[]{cnt}, new float[]{calculateMultiThumbsHeight(ratiosCropped, MAX_WIDTH, GAP)});
|
||||
|
||||
// Two lines
|
||||
for(firstLine=1; firstLine<=cnt-1; firstLine++){
|
||||
tries.put(new int[]{firstLine, secondLine=cnt-firstLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), maxW, marginW),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, ratiosCropped.size()), maxW, marginW)
|
||||
tries.put(new int[]{firstLine, cnt-firstLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), MAX_WIDTH, GAP),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, ratiosCropped.size()), MAX_WIDTH, GAP)
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -189,23 +206,24 @@ public class PhotoLayoutHelper{
|
||||
// Three lines
|
||||
for(firstLine=1; firstLine<=cnt-2; firstLine++){
|
||||
for(secondLine=1; secondLine<=cnt-firstLine-1; secondLine++){
|
||||
tries.put(new int[]{firstLine, secondLine, thirdLine=cnt-firstLine-secondLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), maxW, marginW),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, firstLine+secondLine), maxW, marginW),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine+secondLine, ratiosCropped.size()), maxW, marginW)
|
||||
tries.put(new int[]{firstLine, secondLine, cnt-firstLine-secondLine}, new float[]{
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(0, firstLine), MAX_WIDTH, GAP),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine, firstLine+secondLine), MAX_WIDTH, GAP),
|
||||
calculateMultiThumbsHeight(ratiosCropped.subList(firstLine+secondLine, ratiosCropped.size()), MAX_WIDTH, GAP)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Looking for minimum difference between thumbs block height and maxH (may probably be little over)
|
||||
// Looking for minimum difference between thumbs block height and maxHeight (may probably be little over)
|
||||
final int realMaxHeight=Math.min(MAX_HEIGHT, MAX_WIDTH);
|
||||
int[] optConf=null;
|
||||
float optDiff=0;
|
||||
for(int[] conf : tries.keySet()){
|
||||
float[] heights=tries.get(conf);
|
||||
float confH=marginH*(heights.length-1);
|
||||
float confH=GAP*(heights.length-1);
|
||||
for(float h : heights) confH+=h;
|
||||
float confDiff=Math.abs(confH-maxH);
|
||||
float confDiff=Math.abs(confH-realMaxHeight);
|
||||
if(conf.length>1){
|
||||
if(conf[0]>conf[1] || conf.length>2 && conf[1]>conf[2]){
|
||||
confDiff*=1.1;
|
||||
@@ -222,7 +240,7 @@ public class PhotoLayoutHelper{
|
||||
float[] optHeights=tries.get(optConf);
|
||||
int k=0;
|
||||
|
||||
result.width=Math.round(maxW);
|
||||
result.width=MAX_WIDTH;
|
||||
result.rowSizes=new int[optHeights.length];
|
||||
result.tiles=new TiledLayoutResult.Tile[thumbs.size()];
|
||||
float totalHeight=0f;
|
||||
@@ -240,11 +258,11 @@ public class PhotoLayoutHelper{
|
||||
ArrayList<TiledLayoutResult.Tile> row=new ArrayList<>();
|
||||
for(int j=0; j<lineThumbs.size(); j++){
|
||||
float thumb_ratio=ratiosRemain.remove(0);
|
||||
float w=j==lineThumbs.size()-1 ? (maxW-totalWidth) : (thumb_ratio*lineHeight);
|
||||
float w=j==lineThumbs.size()-1 ? (MAX_WIDTH-totalWidth) : (thumb_ratio*lineHeight);
|
||||
totalWidth+=Math.round(w);
|
||||
if(j<lineThumbs.size()-1 && !gridLineOffsets.contains(totalWidth))
|
||||
gridLineOffsets.add(totalWidth);
|
||||
TiledLayoutResult.Tile tile=new TiledLayoutResult.Tile(1, 1, w, lineHeight, 0, i);
|
||||
TiledLayoutResult.Tile tile=new TiledLayoutResult.Tile(1, 1, 0, i, Math.round(w));
|
||||
result.tiles[k]=tile;
|
||||
row.add(tile);
|
||||
k++;
|
||||
@@ -252,7 +270,7 @@ public class PhotoLayoutHelper{
|
||||
rowTiles.add(row);
|
||||
}
|
||||
Collections.sort(gridLineOffsets);
|
||||
gridLineOffsets.add(Math.round(maxW));
|
||||
gridLineOffsets.add(Math.round(MAX_WIDTH));
|
||||
result.columnSizes=new int[gridLineOffsets.size()];
|
||||
result.columnSizes[0]=gridLineOffsets.get(0);
|
||||
for(int i=gridLineOffsets.size()-1; i>0; i--){
|
||||
@@ -276,7 +294,7 @@ public class PhotoLayoutHelper{
|
||||
columnOffset+=tile.colSpan;
|
||||
}
|
||||
}
|
||||
result.height=Math.round(totalHeight+marginH*(optHeights.length-1));
|
||||
result.height=Math.round(totalHeight+GAP*(optHeights.length-1));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -310,19 +328,19 @@ public class PhotoLayoutHelper{
|
||||
}
|
||||
|
||||
public static class Tile{
|
||||
public int colSpan, rowSpan, width, height, startCol, startRow;
|
||||
public int colSpan, rowSpan, startCol, startRow;
|
||||
public int width;
|
||||
|
||||
public Tile(int colSpan, int rowSpan, int width, int height, int startCol, int startRow){
|
||||
public Tile(int colSpan, int rowSpan, int startCol, int startRow){
|
||||
this.colSpan=colSpan;
|
||||
this.rowSpan=rowSpan;
|
||||
this.width=width;
|
||||
this.height=height;
|
||||
this.startCol=startCol;
|
||||
this.startRow=startRow;
|
||||
}
|
||||
|
||||
public Tile(int colSpan, int rowSpan, float width, float height, int startCol, int startRow){
|
||||
this(colSpan, rowSpan, Math.round(width), Math.round(height), startCol, startRow);
|
||||
public Tile(int colSpan, int rowSpan, int startCol, int startRow, int width){
|
||||
this(colSpan, rowSpan, startCol, startRow);
|
||||
this.width=width;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -330,8 +348,8 @@ public class PhotoLayoutHelper{
|
||||
return "Tile{"+
|
||||
"colSpan="+colSpan+
|
||||
", rowSpan="+rowSpan+
|
||||
", width="+width+
|
||||
", height="+height+
|
||||
", startCol="+startCol+
|
||||
", startRow="+startRow+
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
|
||||
followersLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.followers, (int)Math.min(999, item.account.followersCount)));
|
||||
followingLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.following, (int)Math.min(999, item.account.followingCount)));
|
||||
postsLabel.setText(item.parentFragment.getResources().getQuantityString(R.plurals.posts, (int)Math.min(999, item.account.statusesCount)));
|
||||
followersCount.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followersLabel.setVisibility(item.account.followersCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingCount.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
followingLabel.setVisibility(item.account.followingCount < 0 ? View.GONE : View.VISIBLE);
|
||||
relationship=item.parentFragment.getRelationship(item.account.id);
|
||||
if(item.notification.type == Notification.Type.FOLLOW_REQUEST && (relationship == null || !relationship.followedBy)){
|
||||
actionWrap.setVisibility(View.GONE);
|
||||
|
||||
@@ -76,10 +76,8 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(ExtendedFooterStatusDisplayItem item){
|
||||
Status s=item.status;
|
||||
favorites.setText(context.getResources().getQuantityString(R.plurals.x_favorites, (int)(s.favouritesCount%1000), s.favouritesCount));
|
||||
|
||||
reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int) (s.reblogsCount % 1000), s.reblogsCount));
|
||||
if (!s.canBeBoosted(item.accountID))
|
||||
reblogs.setVisibility(View.GONE);
|
||||
reblogs.setVisibility(s.isReblogPermitted(item.accountID) ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(s.editedAt!=null){
|
||||
editHistory.setVisibility(View.VISIBLE);
|
||||
@@ -88,7 +86,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
editHistory.setVisibility(View.GONE);
|
||||
}
|
||||
String timeStr=item.status.createdAt != null ? TIME_FORMATTER.format(item.status.createdAt.atZone(ZoneId.systemDefault())) : null;
|
||||
|
||||
|
||||
if (item.status.application!=null && !TextUtils.isEmpty(item.status.application.name)) {
|
||||
time.setText(timeStr != null ? item.parentFragment.getString(R.string.timestamp_via_app, timeStr, "") : "");
|
||||
applicationName.setText(item.status.application.name);
|
||||
@@ -143,4 +141,4 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
Nav.go(item.parentFragment.getActivity(), StatusEditHistoryFragment.class, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
boost.setSelected(item.status.reblogged);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
boost.setEnabled(item.status.isReblogPermitted(item.accountID));
|
||||
|
||||
int nextPos = getAbsoluteAdapterPosition() + 1;
|
||||
boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
|
||||
@@ -187,9 +186,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||
longClickPerformed = false;
|
||||
touchingView = v;
|
||||
// 28dp to center in middle of icon, because:
|
||||
// (icon width = 24dp) / 2 + (paddingStart = 8dp) + (paddingHorizontal = 8dp)
|
||||
v.setPivotX(UiUtils.sp(v.getContext(), 28));
|
||||
v.setPivotX(V.sp(28));
|
||||
v.animate().scaleX(0.85f).scaleY(0.85f).setInterpolator(CubicBezierInterpolator.DEFAULT).setDuration(75).start();
|
||||
if (disabled) return true;
|
||||
v.postDelayed(longClickRunnable, ViewConfiguration.getLongPressTimeout());
|
||||
|
||||
@@ -425,6 +425,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
else collapseBtnIcon.animate().scaleY(item.status.textExpanded ? -1 : 1).start();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) collapseBtn.setTooltipText(collapseText);
|
||||
}
|
||||
|
||||
itemView.setPaddingRelative(itemView.getPaddingStart(), itemView.getPaddingTop(),
|
||||
item.inset ? V.dp(10) : V.dp(4), itemView.getPaddingBottom());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.app.Activity;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
@@ -25,7 +26,9 @@ import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.PhotoLayoutHelper;
|
||||
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
|
||||
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
import org.joinmastodon.android.ui.views.FrameLayoutThatOnlyMeasuresFirstChild;
|
||||
import org.joinmastodon.android.ui.views.MaxWidthFrameLayout;
|
||||
import org.joinmastodon.android.ui.views.MediaGridLayout;
|
||||
import org.joinmastodon.android.utils.TypedObjectPool;
|
||||
|
||||
@@ -88,6 +91,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
private final View.OnClickListener clickListener=this::onViewClick, altTextClickListener=this::onAltTextClick;
|
||||
private final ArrayList<MediaAttachmentViewController> controllers=new ArrayList<>();
|
||||
|
||||
private final MaxWidthFrameLayout overlays;
|
||||
private final FrameLayout altTextWrapper;
|
||||
private final TextView altTextButton;
|
||||
private final ImageView noAltTextButton;
|
||||
@@ -105,8 +109,13 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
wrapper=(FrameLayout)itemView;
|
||||
layout=new MediaGridLayout(activity);
|
||||
wrapper.addView(layout);
|
||||
wrapper.setClipToPadding(false);
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, wrapper);
|
||||
overlays=new MaxWidthFrameLayout(activity);
|
||||
overlays.setMaxWidth(UiUtils.MAX_WIDTH);
|
||||
wrapper.addView(overlays, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL));
|
||||
|
||||
activity.getLayoutInflater().inflate(R.layout.overlay_image_alt_text, overlays);
|
||||
altTextWrapper=findViewById(R.id.alt_text_wrapper);
|
||||
altTextButton=findViewById(R.id.alt_button);
|
||||
noAltTextButton=findViewById(R.id.no_alt_button);
|
||||
@@ -215,7 +224,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
int[] loc={0, 0};
|
||||
v.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
wrapper.getLocationInWindow(loc);
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
@@ -278,7 +287,7 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
|
||||
int[] loc={0, 0};
|
||||
btn.getLocationInWindow(loc);
|
||||
int btnL=loc[0], btnT=loc[1];
|
||||
wrapper.getLocationInWindow(loc);
|
||||
overlays.getLocationInWindow(loc);
|
||||
btnL-=loc[0];
|
||||
btnT-=loc[1];
|
||||
|
||||
|
||||
@@ -105,6 +105,21 @@ public abstract class StatusDisplayItem{
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
|
||||
}
|
||||
|
||||
public static ReblogOrReplyLineStatusDisplayItem buildReplyLine(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parent, Account account, boolean threadReply) {
|
||||
String parentID = parent.getID();
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
return new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
);
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
||||
String parentID=parentObject.getID();
|
||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||
@@ -120,17 +135,7 @@ public abstract class StatusDisplayItem{
|
||||
|
||||
if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){
|
||||
Account account = knownAccounts.get(statusForContent.inReplyToAccountId);
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
replyLine = new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
);
|
||||
replyLine = buildReplyLine(fragment, status, accountID, parentObject, account, threadReply);
|
||||
}
|
||||
|
||||
if(status.reblog!=null){
|
||||
|
||||
@@ -1307,7 +1307,7 @@ public class UiUtils {
|
||||
go.accept(ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
go.accept(null, bundleError(context.getString(R.string.sk_resource_not_found)));
|
||||
go.accept(null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1499,16 +1499,6 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale the input value according to the device's scaled display density
|
||||
* @param sp Input value in scale-independent pixels (sp)
|
||||
* @return Scaled value in physical pixels (px)
|
||||
*/
|
||||
public static int sp(Context context, float sp){
|
||||
// TODO: replace with V.sp in next AppKit version
|
||||
return Math.round(sp*context.getApplicationContext().getResources().getDisplayMetrics().scaledDensity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a View.OnClickListener to filter multiple clicks in succession.
|
||||
* Useful for buttons that perform some action that changes their state asynchronously.
|
||||
|
||||
@@ -13,7 +13,7 @@ import me.grishka.appkit.utils.V;
|
||||
public class MediaGridLayout extends ViewGroup{
|
||||
private static final String TAG="MediaGridLayout";
|
||||
|
||||
private static final int GAP=1; // dp
|
||||
private static final int GAP=2; // dp
|
||||
private PhotoLayoutHelper.TiledLayoutResult tiledLayout;
|
||||
private int[] columnStarts=new int[10], columnEnds=new int[10], rowStarts=new int[10], rowEnds=new int[10];
|
||||
|
||||
@@ -37,6 +37,9 @@ public class MediaGridLayout extends ViewGroup{
|
||||
}
|
||||
int width=Math.min(UiUtils.MAX_WIDTH, MeasureSpec.getSize(widthMeasureSpec));
|
||||
int height=Math.round(width*(tiledLayout.height/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
if(tiledLayout.width<PhotoLayoutHelper.MAX_WIDTH){
|
||||
width=Math.round(width*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
}
|
||||
|
||||
int offset=0;
|
||||
for(int i=0;i<tiledLayout.columnSizes.length;i++){
|
||||
@@ -73,9 +76,13 @@ public class MediaGridLayout extends ViewGroup{
|
||||
if(tiledLayout==null)
|
||||
return;
|
||||
|
||||
int maxWidth=UiUtils.MAX_WIDTH;
|
||||
if(tiledLayout.width<PhotoLayoutHelper.MAX_WIDTH){
|
||||
maxWidth=Math.round((r-l)*(tiledLayout.width/(float)PhotoLayoutHelper.MAX_WIDTH));
|
||||
}
|
||||
int xOffset=0;
|
||||
if(r-l>UiUtils.MAX_WIDTH){
|
||||
xOffset=(r-l)/2-UiUtils.MAX_WIDTH/2;
|
||||
if(r-l>maxWidth){
|
||||
xOffset=(r-l)/2-maxWidth/2;
|
||||
}
|
||||
|
||||
for(int i=0;i<getChildCount();i++){
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,79 +6,79 @@
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingStart="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/more"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_more_vertical_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/delete_notification"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/more"
|
||||
android:visibility="gone"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/sk_delete_notification"
|
||||
android:tooltipText="@string/sk_delete_notification"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_dismiss_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/visibility"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/delete_notification"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_visibility"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/collapse_btn"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/visibility"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:visibility="gone"
|
||||
android:importantForAccessibility="noHideDescendants">
|
||||
|
||||
<!-- wrapping this button so the flip animation doesn't flip the background and the tooltip
|
||||
isn't displaced by the -1 scale -->
|
||||
android:layout_alignParentEnd="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collapse_btn_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:id="@+id/unread_indicator"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:visibility="gone"
|
||||
android:tint="?android:colorAccent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_chevron_down_20_filled"
|
||||
android:src="@drawable/ic_fluent_circle_small_20_filled" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/collapse_btn"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:visibility="gone"
|
||||
android:importantForAccessibility="noHideDescendants">
|
||||
|
||||
<!-- wrapping this button so the flip animation doesn't flip the background and the tooltip
|
||||
isn't displaced by the -1 scale -->
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/collapse_btn_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_chevron_down_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/visibility"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_visibility"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/delete_notification"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:visibility="gone"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/sk_delete_notification"
|
||||
android:tooltipText="@string/sk_delete_notification"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_dismiss_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/more"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?android:actionBarItemBackground"
|
||||
android:contentDescription="@string/more_options"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_more_vertical_20_filled"
|
||||
android:tint="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/unread_indicator"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="13dp"
|
||||
android:layout_toStartOf="@id/collapse_btn"
|
||||
android:visibility="gone"
|
||||
android:tint="?android:colorAccent"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_fluent_circle_small_20_filled" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatar"
|
||||
@@ -94,7 +94,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_toStartOf="@id/unread_indicator"
|
||||
android:layout_toStartOf="@id/buttons"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_above="@+id/username_wrap">
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_toStartOf="@id/unread_indicator"
|
||||
android:layout_toStartOf="@id/buttons"
|
||||
android:layout_toEndOf="@id/avatar"
|
||||
android:layout_alignBottom="@id/avatar"
|
||||
android:layoutDirection="locale"
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="40dp">
|
||||
android:layout_marginEnd="40dp"
|
||||
android:minWidth="40dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/auto_reveal_never"
|
||||
android:title="@string/sk_settings_auto_reveal_never" />
|
||||
android:title="@string/sk_settings_auto_reveal_nobody" />
|
||||
<item
|
||||
android:id="@+id/auto_reveal_threads"
|
||||
android:title="@string/sk_settings_auto_reveal_threads" />
|
||||
android:title="@string/sk_settings_auto_reveal_author" />
|
||||
<item
|
||||
android:id="@+id/auto_reveal_discussions"
|
||||
android:title="@string/sk_settings_auto_reveal_discussions" />
|
||||
android:title="@string/sk_settings_auto_reveal_anyone" />
|
||||
</menu>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/prefix_replies_never" android:title="@string/sk_settings_prefix_replies_never" />
|
||||
<item android:id="@+id/prefix_replies_to_others" android:title="@string/sk_settings_prefix_replies_to_others" />
|
||||
<item android:id="@+id/prefix_replies_always" android:title="@string/sk_settings_prefix_replies_always" />
|
||||
</menu>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_confirm_delete_and_redraft">هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟</string>
|
||||
<string name="sk_visibility_unlisted">غير مدرج</string>
|
||||
<string name="sk_list_timelines">القوائم</string>
|
||||
<string name="sk_follow_requests">طلبات المتابعة</string>
|
||||
<string name="sk_pinned_posts">مدبّس</string>
|
||||
<string name="sk_delete_and_redraft">حذف وإعادة الصياغة</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">حذف وإعادة صياغة الرسالة</string>
|
||||
<string name="sk_pin_post">تدبيس على الصفحة الشخصية</string>
|
||||
<string name="sk_confirm_pin_post_title">تدبيس الرسالة على الصفحة الشخصية</string>
|
||||
<string name="sk_settings_show_federated_timeline">إظهار الخيط الفديرالي</string>
|
||||
<string name="sk_settings_contribute">المساهمة في Megalodon</string>
|
||||
<string name="sk_accept_follow_request">قبول طلب المتابعة</string>
|
||||
<string name="sk_reject_follow_request">رفض طلب المتابعة</string>
|
||||
<string name="sk_lists_with_user">قوائم بها %s</string>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -2,7 +2,16 @@
|
||||
<resources>
|
||||
<string name="sk_confirm_delete_and_redraft">هل أنت متأكد أنك تريد حذف وإعادة صياغة هذا المنشور؟</string>
|
||||
<string name="sk_pin_post">تثبيت في الملف الشخصي</string>
|
||||
<string name="sk_pinned_posts">مثبت</string>
|
||||
<string name="sk_pinned_posts">المُثَبَّتَة</string>
|
||||
<string name="sk_delete_and_redraft">حذف وإعادة صياغة</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">حذف وإعادة صياغة المنشور</string>
|
||||
<string name="sk_visibility_unlisted">غير مدرج</string>
|
||||
<string name="sk_list_timelines">القوائم</string>
|
||||
<string name="sk_follow_requests">طلبات المتابعة</string>
|
||||
<string name="sk_confirm_pin_post_title">تدبيس الرسالة على الصفحة الشخصية</string>
|
||||
<string name="sk_settings_show_federated_timeline">إظهار الخيط الفديرالي</string>
|
||||
<string name="sk_settings_contribute">المساهمة في Megalodon</string>
|
||||
<string name="sk_accept_follow_request">قبول طلب المتابعة</string>
|
||||
<string name="sk_reject_follow_request">رفض طلب المتابعة</string>
|
||||
<string name="sk_lists_with_user">قوائم بها %s</string>
|
||||
</resources>
|
||||
@@ -101,7 +101,7 @@
|
||||
<string name="sk_already_reblogged">Bereits geteilt</string>
|
||||
<string name="sk_reply_as">Antworten mit anderem Konto</string>
|
||||
<string name="sk_settings_uniform_icon_for_notifications">Einheitliches Icon für alle Benachrichtigungen</string>
|
||||
<string name="sk_forward_report_to">Weiterleiten zu %s</string>
|
||||
<string name="sk_forward_report_to">Weiterleiten an %s</string>
|
||||
<string name="sk_unsent_posts">Nicht gesendete Beiträge</string>
|
||||
<string name="sk_draft">Entwurf</string>
|
||||
<string name="sk_schedule">Planen</string>
|
||||
@@ -250,7 +250,7 @@
|
||||
<string name="sk_settings_see_new_posts_button">“Neue Beiträge anzeigen”-Button</string>
|
||||
<string name="sk_settings_server_version">Server-Version: %s</string>
|
||||
<string name="sk_notify_poll_results">Umfrage-Ergebnisse</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">CWs beim Antworten “re:” voranstellen</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Voranstellen von „re:“ an CWs in Antworten an</string>
|
||||
<string name="sk_filtered">Gefiltert: %s</string>
|
||||
<string name="sk_expand">Erweitern</string>
|
||||
<string name="sk_collapse">Einklappen</string>
|
||||
@@ -296,4 +296,12 @@
|
||||
<string name="sk_no_remote_info_hint">keine Remote-Infos abrufbar</string>
|
||||
<string name="sk_error_loading_profile">Konnte das Profil via %s nicht laden</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Für vollständigere Auflistung von Follower*innen, Likes und Boosts können die Informationen von der Ursprungs-Instanz geladen werden.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Zeigen von gleichen CWs in Antworten von</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">niemandem</string>
|
||||
<string name="sk_settings_auto_reveal_author">Autor*in</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">allen</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">andere</string>
|
||||
<string name="sk_settings_prefix_replies_always">alle</string>
|
||||
<string name="sk_settings_prefix_replies_never">niemanden</string>
|
||||
<string name="sk_settings_forward_report_default">Standardwert „Meldung weiterleiten“-Schalter</string>
|
||||
</resources>
|
||||
@@ -245,9 +245,9 @@
|
||||
<string name="sk_settings_glitch_instance">Glitch modo sólo local</string>
|
||||
<string name="sk_settings_glitch_mode_explanation">Habilita esta opción si tu instancia local funciona con Glitch. No es necesario para Hometown o Akkoma.</string>
|
||||
<string name="sk_signed_up">registrado</string>
|
||||
<string name="sk_reported">reportado</string>
|
||||
<string name="sk_reported">denunciado</string>
|
||||
<string name="sk_sign_ups">Registro de usuarios</string>
|
||||
<string name="sk_new_reports">Nuevos informes</string>
|
||||
<string name="sk_new_reports">Nuevas denuncias</string>
|
||||
<string name="sk_settings_server_version">Versión de servidor: %s</string>
|
||||
<string name="sk_notify_poll_results">Resultado de encuestas</string>
|
||||
<string name="sk_filtered">Filtrado: %s</string>
|
||||
@@ -256,7 +256,7 @@
|
||||
<string name="sk_settings_collapse_long_posts">Minimizar publicaciones largas</string>
|
||||
<string name="sk_unfinished_attachments">¿Corregir adjuntos\?</string>
|
||||
<string name="sk_unfinished_attachments_message">Algunos adjuntos no han terminado de subirse.</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re:\" a respuestas a Advertencias de Contenido</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Añadir \"re:\" a Advertencias de Contenido para</string>
|
||||
<string name="sk_spectator_mode">Modo espectador</string>
|
||||
<string name="sk_settings_hide_interaction">Ocultar los botones de interacción</string>
|
||||
<string name="sk_follow_as">Seguir desde otra cuenta</string>
|
||||
@@ -286,10 +286,23 @@
|
||||
<string name="sk_settings_default_content_type">Contenido por defecto</string>
|
||||
<string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Estas son las publicaciones más recientes de la gente en tu servidor de Akkoma.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Estas son las publicaciones más recientes de la red seleccionadas por los administradores de tu instancia.</string>
|
||||
<string name="sk_timeline_bubble">Burbuja</string>
|
||||
<string name="sk_instance_info_unavailable">Información de la instancia temporalmente no disponible</string>
|
||||
<string name="sk_external_share_or_open_title">Compartir o abrir con una cuenta</string>
|
||||
<string name="sk_open_in_app">Abrir en la app</string>
|
||||
<string name="sk_external_share_title">Compartir con una cuenta</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Mostrar CW iguales en las respuestas de</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">nadie</string>
|
||||
<string name="sk_settings_auto_reveal_author">autor</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">todos</string>
|
||||
<string name="sk_open_in_app_failed">No se pudo abrir en la aplicación</string>
|
||||
<string name="sk_no_remote_info_hint">no hay información remota disponible</string>
|
||||
<string name="sk_error_loading_profile">No se pudo cargar el perfil a través de %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Cargar la información desde las instancias remotas</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Intenta obtener listas más precisas de seguidores, Me gusta y promociones cargando la información desde la instancia de origen.</string>
|
||||
<string name="sk_settings_prefix_replies_always">Todas</string>
|
||||
<string name="sk_settings_prefix_replies_never">Ninguna</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">A otros</string>
|
||||
<string name="sk_settings_forward_report_default">\"Reenviar denuncia\" activado por defecto</string>
|
||||
</resources>
|
||||
@@ -12,51 +12,85 @@
|
||||
<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>
|
||||
<string name="time_hours">%dساعت</string>
|
||||
<string name="time_days">%dروز</string>
|
||||
<string name="share_toot_title">اشتراکگذاری</string>
|
||||
<string name="share_toot_title">همرسانی</string>
|
||||
<string name="settings">تنظیمات</string>
|
||||
<string name="publish">انتشار</string>
|
||||
<string name="discard_draft">پیشنویس کنار گذاشته شود؟</string>
|
||||
<string name="discard">صرفنظر کردن</string>
|
||||
<string name="cancel">لغو</string>
|
||||
<string name="posts">پستها</string>
|
||||
<string name="posts_and_replies">پستها و پاسخها</string>
|
||||
<plurals name="followers">
|
||||
<item quantity="one">پیگیرنده</item>
|
||||
<item quantity="other">پیگیرندگان</item>
|
||||
</plurals>
|
||||
<plurals name="posts">
|
||||
<item quantity="one">فرسته</item>
|
||||
<item quantity="other">فرستهها</item>
|
||||
</plurals>
|
||||
<string name="posts">فرستهها</string>
|
||||
<string name="posts_and_replies">فرستهها و پاسخها</string>
|
||||
<string name="media">رسانه</string>
|
||||
<string name="profile_about">درباره</string>
|
||||
<string name="button_follow">فالو</string>
|
||||
<string name="button_follow">پیگیری</string>
|
||||
<string name="edit_profile">ویرایش نمایه</string>
|
||||
<string name="share_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="report_user">گزارش کردن %s</string>
|
||||
<string name="profile_joined">عضو شد</string>
|
||||
<string name="done">انجام شد</string>
|
||||
<string name="loading">درحال بارگذاری…</string>
|
||||
<string name="field_label">برچسب</string>
|
||||
<string name="field_content">محتوا</string>
|
||||
<string name="saving">درحال ذخیرهسازی…</string>
|
||||
<string name="poll_closed">پایانیافته</string>
|
||||
<string name="confirm_mute_title">خموشی حساب</string>
|
||||
<string name="do_mute">بیصدا</string>
|
||||
<string name="confirm_unmute_title">لغو خموشی حساب</string>
|
||||
<string name="do_unmute">ناخموشی</string>
|
||||
<string name="confirm_block_title">مسدود کردن حساب</string>
|
||||
<string name="confirm_block_domain_title">مسدود کردن دامنهٔ</string>
|
||||
<string name="do_block">مسدود</string>
|
||||
<string name="confirm_unblock_title">رفع مسدودی حساب</string>
|
||||
<string name="confirm_unblock_domain_title">رفع مسدودیت دامنهٔ</string>
|
||||
<string name="do_unblock">رفع مسدودیت</string>
|
||||
<string name="button_muted">خموش</string>
|
||||
<string name="button_blocked">مسدود شده</string>
|
||||
<string name="action_vote">رأی</string>
|
||||
<string name="delete">حذف</string>
|
||||
<string name="confirm_delete_title">حذف پست</string>
|
||||
<string name="confirm_delete_title">حذف فرسته</string>
|
||||
<string name="play">پخش</string>
|
||||
<string name="pause">توقف</string>
|
||||
<string name="log_out">خروج</string>
|
||||
<string name="add_account">افزودن حساب</string>
|
||||
<string name="search_hint">جستجو</string>
|
||||
<string name="hashtags">هشتگها</string>
|
||||
<string name="hashtags">برچسبها</string>
|
||||
<string name="news">اخبار</string>
|
||||
<string name="for_you">برای شما</string>
|
||||
<string name="all_notifications">همه</string>
|
||||
<string name="report_title">گزارش کردن %s</string>
|
||||
<string name="report_reason_personal">من این را دوست ندارم</string>
|
||||
<string name="report_reason_spam">این هرزنامه است</string>
|
||||
<string name="sending_report">درحال ارسال گزارش…</string>
|
||||
<string name="unfollow">پینگرفتن</string>
|
||||
<string name="back">بازگشت</string>
|
||||
<string name="instance_rules_title">قوانین کارساز</string>
|
||||
<string name="signup_title">ایجاد حساب</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="confirm_password">تأیید گذرواژه</string>
|
||||
<string name="category_activism">فعالیت</string>
|
||||
<string name="category_all">همه</string>
|
||||
<string name="category_art">هنر</string>
|
||||
@@ -68,34 +102,118 @@
|
||||
<string name="category_tech">فناوری</string>
|
||||
<!-- %s is the email address -->
|
||||
<string name="resend">ارسال دوباره</string>
|
||||
<string name="retry_upload">بارگذاری مجدد</string>
|
||||
<string name="edit_image">ویرایش تصویر</string>
|
||||
<string name="save">ذخیره</string>
|
||||
<string name="visibility_public">عمومی</string>
|
||||
<string name="visibility_followers_only">فقط پیگیرندگان</string>
|
||||
<string name="search_all">همه</string>
|
||||
<string name="search_people">افراد</string>
|
||||
<string name="skip">بعدی</string>
|
||||
<string name="notification_type_favorite">علاقهمندیها</string>
|
||||
<string name="notification_type_reblog">تقویتها</string>
|
||||
<string name="notification_type_poll">نظرسنجیها</string>
|
||||
<string name="choose_account">انتخاب حساب</string>
|
||||
<string name="theme_auto">خودکار</string>
|
||||
<string name="theme_light">روشن</string>
|
||||
<string name="theme_dark">تاریک</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="add_media">افزودن رسانه</string>
|
||||
<string name="add_poll">افزودن نظرسنجی</string>
|
||||
<string name="emoji">ایموجی</string>
|
||||
<string name="my_profile">نمایه من</string>
|
||||
<string name="follow_user">پیگیری %s</string>
|
||||
<string name="unfollowed_user">پینگرفتن %s</string>
|
||||
<string name="open_in_browser">بازکردن در مرورگر</string>
|
||||
<string name="clear">پاککردن</string>
|
||||
<string name="download">دانلود</string>
|
||||
<string name="download">بارگیری</string>
|
||||
<string name="open_settings">باز کردن تنظیمات</string>
|
||||
<string name="file_saved">پرونده ذخیره شد</string>
|
||||
<string name="downloading">درحال بارگیری…</string>
|
||||
<string name="local_timeline">اجتماع</string>
|
||||
<string name="follows_you">پیگیرتان است</string>
|
||||
<!-- translators: %,d is a valid placeholder, it formats the number with locale-dependent grouping separators -->
|
||||
<plurals name="x_favorites">
|
||||
<item quantity="one">%,d پسندیده</item>
|
||||
<item quantity="other">%,d پسندیدهها</item>
|
||||
</plurals>
|
||||
<string name="time_now">اکنون</string>
|
||||
<string name="post_info_reblogs">تقویتها</string>
|
||||
<string name="post_info_favorites">علاقهمندیها</string>
|
||||
<string name="last_edit_at_x">آخرین ویرایش %s</string>
|
||||
<string name="time_just_now">همين الان</string>
|
||||
<plurals name="x_seconds_ago">
|
||||
<item quantity="one">%d ثانیه پیش</item>
|
||||
<item quantity="other">%d ثانیه پیش</item>
|
||||
</plurals>
|
||||
<plurals name="x_minutes_ago">
|
||||
<item quantity="one">%d دقیقه پیش</item>
|
||||
<item quantity="other">%d دقیقه پیش</item>
|
||||
</plurals>
|
||||
<string name="edit_original_post">فرستهٔ اصلی</string>
|
||||
<string name="edit_text_edited">متن ویرایش شد</string>
|
||||
<string name="edit_spoiler_added">هشدار محتوا افزوده شد</string>
|
||||
<string name="edit_spoiler_edited">هشدار محتوا ویرایش شد</string>
|
||||
<string name="edit_spoiler_removed">هشدار محتوا برداشته شد</string>
|
||||
<string name="edit_poll_added">نظرسنجی اضافه شد</string>
|
||||
<string name="edit_poll_edited">نظرسنجی ویرایش شد</string>
|
||||
<string name="edit_poll_removed">نظرسنجی برداشته شد</string>
|
||||
<string name="edit_media_added">رسانه اضافه شد</string>
|
||||
<string name="edit_media_removed">رسانه برداشته شد</string>
|
||||
<string name="edit_multiple_changed">فرسته ویرایش شد</string>
|
||||
<string name="edit">ویرایش</string>
|
||||
<string name="upload_failed">بارگذاری ناموفق بود</string>
|
||||
<string name="file_size_bytes">%d بایت</string>
|
||||
<string name="file_size_kb">%.2f کیلوبایت</string>
|
||||
<string name="file_size_mb">%.2f مگابایت</string>
|
||||
<string name="file_size_gb">%.2f گیگابایت</string>
|
||||
<string name="upload_processing">در حال پردازش…</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<string name="update_available">ماستودون برای اندروید %s آماده بارگیری است.</string>
|
||||
<!-- %s is version like 1.2.3 -->
|
||||
<string name="update_ready">ماستودون برای اندروید %s بارگیری شده و آماده نصب است.</string>
|
||||
<!-- %s is file size -->
|
||||
<string name="download_update">بارگیری (%s)</string>
|
||||
<string name="install_update">نصب</string>
|
||||
<string name="privacy_policy_title">حریم خصوصی شما</string>
|
||||
<string name="i_agree">موافقم</string>
|
||||
<string name="text_copied">در تختهگیره رونوشت شد</string>
|
||||
<string name="add_bookmark">نشانک</string>
|
||||
<string name="remove_bookmark">برداشتن نشانک</string>
|
||||
<string name="bookmarks">نشانکها</string>
|
||||
<string name="your_favorites">علاقهمندی های شما</string>
|
||||
<string name="server_filter_any_language">هر زبانی</string>
|
||||
<string name="server_filter_region_europe">اروپا</string>
|
||||
<string name="server_filter_region_north_america">آمریکای شمالی</string>
|
||||
<string name="server_filter_region_south_america">آمریکای جنوبی</string>
|
||||
<string name="server_filter_region_africa">آفریقا</string>
|
||||
<string name="server_filter_region_asia">آسیا</string>
|
||||
<string name="server_filter_region_oceania">اقیانوسیه</string>
|
||||
<string name="profile_add_row">افزودن سطر</string>
|
||||
<string name="profile_setup">تنظیم نمایه</string>
|
||||
<string name="popular_on_mastodon">محبوب در ماستودون</string>
|
||||
<string name="follow_all">پیگیری همه</string>
|
||||
<!-- %s is server domain -->
|
||||
<string name="profile_bio">دربارهٔ شما</string>
|
||||
<!-- Shown in a progress dialog when you tap "follow all" -->
|
||||
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
|
||||
<string name="spoiler_show">به هرصورت نشان داده شود</string>
|
||||
<string name="spoiler_hide">نهفتن دوباره</string>
|
||||
<string name="poll_multiple_choice">یک یا چند مورد را انتخاب کنید</string>
|
||||
<string name="save_changes">ذخیرهٔ تغییرات</string>
|
||||
<string name="profile_timeline">خط زمانی</string>
|
||||
<string name="view_all">مشاهده همه</string>
|
||||
<string name="profile_endorsed_accounts">حسابها</string>
|
||||
<string name="verified_link">پیوند تأییدشده</string>
|
||||
<string name="show">نمایش</string>
|
||||
<string name="hide">نهفتن</string>
|
||||
<string name="join_default_server">%s پیوست</string>
|
||||
<string name="pick_server">انتخاب کارسازی دیگر</string>
|
||||
<string name="signup_or_login">یا</string>
|
||||
<string name="learn_more">بیشتر بیاموزید</string>
|
||||
<string name="welcome_to_mastodon">به ماستودون خوش آمدید</string>
|
||||
</resources>
|
||||
|
||||
73
mastodon/src/main/res/values-fa/strings_sk.xml
Normal file
73
mastodon/src/main/res/values-fa/strings_sk.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_pinned_posts">سنجاق شده</string>
|
||||
<string name="sk_confirm_pin_post_title">سنجاق فرسته به نمایه</string>
|
||||
<string name="sk_app_name">مگالودون</string>
|
||||
<string name="sk_pin_post">سنجاق به نمایه</string>
|
||||
<string name="sk_pinning">درحال سنجاق کردن فرسته…</string>
|
||||
<string name="sk_unpin_post">برداشتن سنجاق از نمایه</string>
|
||||
<string name="sk_confirm_unpin_post_title">برداشتن سنجاق فرسته از نمایه</string>
|
||||
<string name="sk_unpinning">درحال برداشتن سنجاق فرسته…</string>
|
||||
<string name="sk_image_description">توضیحات تصویر</string>
|
||||
<string name="sk_visibility_unlisted">فهرست نشده</string>
|
||||
<string name="sk_settings_show_replies">نمایش پاسخها</string>
|
||||
<string name="sk_list_timelines">سیاههها</string>
|
||||
<string name="sk_notification_type_status">فرستهها</string>
|
||||
<string name="sk_no_update_available">بهروزرسانی موجود نیست</string>
|
||||
<string name="sk_settings_reply_visibility_all">همه پاسخها</string>
|
||||
<string name="sk_mark_media_as_sensitive">علامتگذاری رسانه به عنوان حساس</string>
|
||||
<string name="sk_update_available">مگالودون %s آماده بارگیری است.</string>
|
||||
<string name="sk_follow_requests">درخواستهای پیگیری</string>
|
||||
<string name="sk_accept_follow_request">پذیرفتن درخواست پیگیری</string>
|
||||
<string name="sk_reject_follow_request">رد درخواست پیگیری</string>
|
||||
<string name="sk_notify_posts">اعلانهای فرسته</string>
|
||||
<string name="sk_color_palette_material3">سامانه</string>
|
||||
<string name="sk_color_palette_pink">صورتی</string>
|
||||
<string name="sk_color_palette_purple">بنفش</string>
|
||||
<string name="sk_color_palette_green">سبز</string>
|
||||
<string name="sk_color_palette_blue">آبی</string>
|
||||
<string name="sk_color_palette_brown">قهوهای</string>
|
||||
<string name="sk_color_palette_red">قرمز</string>
|
||||
<string name="sk_color_palette_yellow">زرد</string>
|
||||
<string name="sk_translate_post">ترجمه</string>
|
||||
<string name="sk_post_language">زبان: %s</string>
|
||||
<string name="sk_available_languages">زبان های موجود</string>
|
||||
<string name="sk_welcome_title">خوش آمدید!</string>
|
||||
<string name="sk_language_name">%1$s (%2$s)</string>
|
||||
<string name="sk_settings_profile">تنظیم نمایه</string>
|
||||
<string name="sk_settings_filters">پیکربندی پالایه</string>
|
||||
<string name="sk_settings_auth">تنظیمات امنیتی</string>
|
||||
<string name="sk_settings_about">درباره کاره</string>
|
||||
<string name="sk_settings_donate">اعانه</string>
|
||||
<string name="sk_clear_all_notifications">پاکسازی همه اعلانات</string>
|
||||
<string name="sk_clear_all_notifications_confirm_action">حذف همه</string>
|
||||
<string name="sk_settings_publish_button_text">متن دکمه انتشار</string>
|
||||
<string name="sk_settings_publish_button_text_title">سفارشیسازی متن دکمه انتشار</string>
|
||||
<string name="sk_undo_reblog">برگردان تقویت</string>
|
||||
<string name="sk_settings_rules">قوانین</string>
|
||||
<string name="sk_collapse">بستن</string>
|
||||
<string name="sk_expand">باز کردن</string>
|
||||
<string name="sk_notify_poll_results">نتایج نظرسنجی</string>
|
||||
<string name="sk_settings_server_version">نسخه کارساز: %s</string>
|
||||
<string name="sk_settings_glitch_instance">حالت فقط محلی Glitch</string>
|
||||
<string name="sk_inline_local_only">فقط محلی</string>
|
||||
<string name="sk_updater_enable_pre_releases">به کار انداختن پیش انتشار</string>
|
||||
<string name="sk_save_draft">ذخیره پیش نویس؟</string>
|
||||
<string name="sk_no_results">بدون نتیجه</string>
|
||||
<string name="sk_searching">درحال جستجو…</string>
|
||||
<string name="sk_attach_file">پیوست پرونده</string>
|
||||
<string name="sk_post_edited">ویرایش شده</string>
|
||||
<string name="sk_content_type_markdown">مارکدون</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_plain">متن ساده</string>
|
||||
<string name="sk_check_for_update">بررسی برای بهروزرسانی</string>
|
||||
<string name="sk_settings_support_local_only">کارساز از فرسته فقط محلی پشتیبانی میکند</string>
|
||||
<string name="sk_settings_show_boosts">نمایش تقویتها</string>
|
||||
<string name="sk_notification_type_update">فرستههای ویرایش شده</string>
|
||||
<string name="sk_update_ready">مگالودون %s بارگیری شده و آماده نصب است.</string>
|
||||
<string name="sk_poll_allow_multiple">اجازه انتخاب های متعدد</string>
|
||||
<string name="sk_open_with_account">باز کردن با حساب دیگر</string>
|
||||
<string name="sk_confirm_delete_draft_title">حذف پیش نویس</string>
|
||||
<string name="sk_draft">پیشنویس</string>
|
||||
<string name="sk_draft_saved">پیش نویس ذخیره شد</string>
|
||||
</resources>
|
||||
@@ -254,7 +254,7 @@
|
||||
<string name="sk_expand">Développer</string>
|
||||
<string name="sk_collapse">Réduire</string>
|
||||
<string name="sk_settings_collapse_long_posts">Réduire les messages très longs</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Préfixe \"re :\" lors d\'une réponse avec AC</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Préfixe les AC avec \"re :\" sur les réponses à</string>
|
||||
<string name="sk_filtered">Filtré : %s</string>
|
||||
<string name="sk_unfinished_attachments">Corriger les pièces jointes \?</string>
|
||||
<string name="sk_unfinished_attachments_message">Certaines pièces jointes n\'ont pas fini de se télécharger.</string>
|
||||
@@ -294,4 +294,16 @@
|
||||
<string name="sk_timeline_bubble">Bulle</string>
|
||||
<string name="sk_instance_info_unavailable">Informations sur l\'instance temporairement indisponibles</string>
|
||||
<string name="sk_open_in_app_failed">Impossible de l\'ouvrir dans l\'application</string>
|
||||
<string name="sk_settings_allow_remote_loading">Charger des informations à partir d\'instances distantes</string>
|
||||
<string name="sk_no_remote_info_hint">informations distantes indisponibles</string>
|
||||
<string name="sk_error_loading_profile">Échec du chargement du profil via %s</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Essayez de récupérer des listes plus précises pour les abonnés, les likes et les boosts en chargeant les informations à partir de l\'instance d\'origine.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Révéler les AC identiques dans les réponses de</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">personne</string>
|
||||
<string name="sk_settings_auto_reveal_author">auteur</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">tout le monde</string>
|
||||
<string name="sk_settings_prefix_replies_always">tout le monde</string>
|
||||
<string name="sk_settings_prefix_replies_never">personne</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">autres</string>
|
||||
<string name="sk_settings_forward_report_default">\"Transférer le rapport\" par défaut</string>
|
||||
</resources>
|
||||
@@ -181,7 +181,7 @@
|
||||
<string name="sk_settings_local_only_explanation">Vaša početna instanca mora podržavati isključivo-lokalno objavljivanje da bi ovo radilo. Većina modificiranih verzija Mastodona to radi, ali Mastodon ne.</string>
|
||||
<string name="sk_settings_hide_interaction">Sakrij gumbe za interakciju</string>
|
||||
<string name="sk_settings_hide_fab">Automatski sakrij gumb \"Nova objava\"</string>
|
||||
<string name="sk_quoting_user">"Citiranje %"</string>
|
||||
<string name="sk_quoting_user">Citiranje %s</string>
|
||||
<string name="sk_settings_reply_visibility">Vidljivost odgovora</string>
|
||||
<string name="sk_settings_reply_visibility_all">Svi odgovori</string>
|
||||
<string name="sk_settings_reply_visibility_following">Odgovori onima koje pratim</string>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<string name="ok">Oke</string>
|
||||
<string name="preparing_auth">Menyiapkan untuk autentikasi…</string>
|
||||
<string name="finishing_auth">Menyelesaikan autentikasi…</string>
|
||||
<string name="user_boosted">%s di-boost-kan</string>
|
||||
<string name="user_boosted">%s membagikan</string>
|
||||
<string name="in_reply_to">Membalas ke %s</string>
|
||||
<string name="notifications">Notifikasi</string>
|
||||
<string name="user_followed_you">mengikuti Anda</string>
|
||||
@@ -417,6 +417,7 @@
|
||||
<string name="show">Tampilkan</string>
|
||||
<string name="hide">Sembunyikan</string>
|
||||
<string name="join_default_server">Gabung %s</string>
|
||||
<string name="pick_server">Cari server lain</string>
|
||||
<string name="signup_or_login">atau</string>
|
||||
<string name="learn_more">Pelajari lebih lanjut</string>
|
||||
<string name="welcome_to_mastodon">Selamat datang di Mastodon</string>
|
||||
|
||||
@@ -289,8 +289,17 @@
|
||||
<string name="sk_settings_content_types_explanation">Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.</string>
|
||||
<string name="sk_open_in_app">Buka dalam aplikasi</string>
|
||||
<string name="sk_external_share_title">Bagikan dengan akun</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yamg paling terkini oleh orang-orang dalam gelembung server Akkoma Anda.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yang paling terkini dari jaringan dikurasikan oleh admin server Anda.</string>
|
||||
<string name="sk_timeline_bubble">Gelembung</string>
|
||||
<string name="sk_instance_info_unavailable">Info server sementara tidak tersedia</string>
|
||||
<string name="sk_external_share_or_open_title">Bagikan atau buka dengan akun</string>
|
||||
<string name="sk_no_remote_info_hint">info jarak jauh tidak tersedia</string>
|
||||
<string name="sk_settings_allow_remote_loading">Muat info dari server jarak jauh</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Coba mendapatkan pendaftaran akurat untuk pengikut cr</string>
|
||||
<string name="sk_error_loading_profile">Gagal memuat profil melalui %s</string>
|
||||
<string name="sk_open_in_app_failed">Tidak dapat buka dalam aplikasi</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Tampilkan peringatan konten yang sama dari</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">bukan siapa pun</string>
|
||||
<string name="sk_settings_auto_reveal_author">pembuat</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">semuanya</string>
|
||||
</resources>
|
||||
@@ -153,7 +153,7 @@
|
||||
<string name="sk_timelines">Timeline</string>
|
||||
<string name="sk_timeline_posts">Post</string>
|
||||
<string name="sk_timelines_add">Aggiungi</string>
|
||||
<string name="sk_timeline">Linea temporale</string>
|
||||
<string name="sk_timeline">Timeline</string>
|
||||
<string name="sk_list">Lista</string>
|
||||
<string name="sk_hashtag">Hashtag</string>
|
||||
<string name="sk_pin_timeline">Fissa timeline</string>
|
||||
@@ -274,8 +274,17 @@
|
||||
<string name="sk_compact_reblog_reply_line">Linea boost/risposta compatta</string>
|
||||
<string name="sk_reacted_with">ha reagito con %s</string>
|
||||
<string name="sk_reply_line_above_avatar">Linea \"In risposta a\" sopra l\'avatar</string>
|
||||
<string name="sk_settings_show_new_posts_button">Tasto \"Mostra nuovi post\"</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Questi sono i post più recenti della rete, curati dagli amministratori della tua istanza.</string>
|
||||
<string name="sk_settings_content_types_explanation">Permette di impostare un tipo di contenuto, come Markdown, quando si crea un post. Tieni presente che non tutte le istanze lo supportano.</string>
|
||||
<string name="sk_open_in_app_failed">Impossibile aprire nell\'app</string>
|
||||
<string name="sk_external_share_title">Condividi con l\'account</string>
|
||||
<string name="sk_external_share_or_open_title">Condividi o apri con l\'account</string>
|
||||
<string name="sk_no_remote_info_hint">Informazioni remote non disponibili</string>
|
||||
<string name="sk_error_loading_profile">Impossibile caricare il profilo tramite %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Carica informazioni da istanze remote</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Cerca di ottenere elenchi più accurati di follower, like e boost caricando le informazioni dall\'istanza di origine.</string>
|
||||
<string name="sk_content_type">Tipo di contenuto</string>
|
||||
<string name="sk_timeline_bubble">Bolla</string>
|
||||
<string name="sk_content_type_unspecified">Non specificato</string>
|
||||
<string name="sk_content_type_plain">Testo semplice</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
@@ -283,7 +292,8 @@
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Abilita la formattazione dei post</string>
|
||||
<string name="sk_settings_content_types_explanation">Permette di impostare un tipo di contenuto, come Markdown, quando si crea un post. Tieni presente che non tutte le istanze lo supportano.</string>
|
||||
<string name="sk_settings_default_content_type">Tipo di contenuto predefinito</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Ti permette di preselezionare un tipo di contenuto quando si creano nuovi post, sovrascrivendo il valore impostato in \"Preferenze di pubblicazione\".</string>
|
||||
<string name="sk_instance_info_unavailable">Informazioni sull\'istanza temporaneamente non disponibili</string>
|
||||
<string name="sk_open_in_app">Apri nell\'app</string>
|
||||
</resources>
|
||||
@@ -254,4 +254,9 @@
|
||||
<string name="sk_quoting_user">Quoting %s</string>
|
||||
<string name="sk_settings_reply_visibility">Zichtbaarheid reactie</string>
|
||||
<string name="sk_settings_reply_visibility_all">Alle reacties</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">iedereen</string>
|
||||
<string name="sk_settings_auto_reveal_author">auteur</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">niemand</string>
|
||||
<string name="sk_settings_prefix_replies_never">niemand</string>
|
||||
<string name="sk_settings_prefix_replies_always">iedereen</string>
|
||||
</resources>
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="sk_app_name">Megalodon</string>
|
||||
<string name="sk_pinned_posts">Fixado</string>
|
||||
<string name="sk_delete_and_redraft">Apagar e reescrever</string>
|
||||
<string name="sk_confirm_delete_and_redraft">Tem a certeza que pretende apagar e reescrever esta publicação\?</string>
|
||||
<string name="sk_confirm_unpin_post">Tem a certeza que deseja desafixar esta publicação\?</string>
|
||||
<string name="sk_settings_reply_visibility_all">Todas as respostas</string>
|
||||
<string name="sk_settings_load_new_posts">Carregar novas publicações automaticamente</string>
|
||||
<string name="sk_user_post_notifications_off">Desligar as notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline_info_banner">Estas são as publicações mais recentes das pessoas na tua federação.</string>
|
||||
<string name="sk_confirm_delete_and_redraft_title">Apagar e reescrever publicação</string>
|
||||
<string name="sk_pin_post">Fixar no perfil</string>
|
||||
<string name="sk_confirm_pin_post_title">Fixar publicação no perfil</string>
|
||||
<string name="sk_confirm_pin_post">Deseja fixar esta publicação ao seu perfil\?</string>
|
||||
<string name="sk_pinning">A fixar a publicação…</string>
|
||||
<string name="sk_unpin_post">Desafixar do perfil</string>
|
||||
<string name="sk_confirm_unpin_post_title">Desafixar publicação do perfil</string>
|
||||
<string name="sk_unpinning">A desafixar publicação…</string>
|
||||
<string name="sk_image_description">Descrição da imagem</string>
|
||||
<string name="sk_visibility_unlisted">Não listado</string>
|
||||
<string name="sk_settings_show_replies">Mostrar respostas</string>
|
||||
<string name="sk_quoting_user">Citação %s</string>
|
||||
<string name="sk_settings_reply_visibility">Visibilidade da resposta</string>
|
||||
<string name="sk_clear_recent_languages">Apagar idiomas usados recentemente</string>
|
||||
<string name="sk_settings_reply_visibility_following">Respostas aos meus comentários</string>
|
||||
<string name="sk_settings_reply_visibility_self">Respostas a mim</string>
|
||||
<string name="sk_settings_show_boosts">Mostrar impulsionamentos</string>
|
||||
<string name="sk_settings_show_interaction_counts">Mostrar contagem de interações</string>
|
||||
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
|
||||
<string name="sk_mark_media_as_sensitive">Marcar conteúdo como sensível</string>
|
||||
<string name="sk_user_post_notifications_on">Ligar as notificações de publicação para %s</string>
|
||||
<string name="sk_federated_timeline">Federação</string>
|
||||
<string name="sk_update_available">Megalodon %s pronto a descarregar.</string>
|
||||
<string name="sk_update_ready">Megalodon %s descarregado e pronto a instalar.</string>
|
||||
<string name="sk_check_for_update">A verificar atualizações</string>
|
||||
<string name="sk_no_update_available">Sem atualizações disponíveis</string>
|
||||
<string name="sk_list_timelines">Listas</string>
|
||||
<string name="sk_follow_requests">Pedidos para seguir</string>
|
||||
<string name="sk_accept_follow_request">Aceitar pedido para seguir</string>
|
||||
<string name="sk_reject_follow_request">Rejeitar pedido para seguir</string>
|
||||
<string name="sk_lists_with_user">Listas com %s</string>
|
||||
<string name="sk_settings_always_reveal_content_warnings">Mostrar sempre avisos de conteúdo</string>
|
||||
</resources>
|
||||
@@ -257,7 +257,7 @@
|
||||
<string name="sk_settings_collapse_long_posts">Сворачивать очень длинные посты</string>
|
||||
<string name="sk_unfinished_attachments">Исправить вложения\?</string>
|
||||
<string name="sk_unfinished_attachments_message">Некоторые вложения еще не загрузились.</string>
|
||||
<string name="sk_quoting_user">Цитирование%s</string>
|
||||
<string name="sk_quoting_user">Цитирование %s</string>
|
||||
<string name="sk_settings_reply_visibility">Видимость ответа</string>
|
||||
<string name="sk_settings_hide_interaction">Скрыть кнопки взаимодействия</string>
|
||||
<string name="sk_follow_as">Подписаться с другого аккаунта</string>
|
||||
|
||||
@@ -269,6 +269,7 @@
|
||||
<string name="hide_content">Dölj innehåll</string>
|
||||
<string name="new_post">Nytt inlägg</string>
|
||||
<string name="button_reply">Svara</string>
|
||||
<string name="button_reblog">Boosta</string>
|
||||
<string name="button_favorite">Favoritmarkera</string>
|
||||
<string name="button_share">Dela</string>
|
||||
<string name="media_no_description">Media utan beskrivning</string>
|
||||
@@ -428,5 +429,7 @@
|
||||
<string name="signup_or_login">eller</string>
|
||||
<string name="learn_more">Läs mer</string>
|
||||
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
|
||||
<string name="welcome_paragraph1">Mastodon är ett decentraliserat socialt nätverk, vilket innebär att inget enskilt företag kontrollerar det. Det består av många oberoende servrar, alla sammankopplade.</string>
|
||||
<string name="what_are_servers">Vad är servrar?</string>
|
||||
<string name="welcome_paragraph2"><![CDATA[Varje Mastodon-konto finns på en server — var och en med sina värderingar, regler och administratörer. Oavsett vilken du väljer kan du följa och interagera med människor på vilken server som helst.]]></string>
|
||||
</resources>
|
||||
|
||||
@@ -293,4 +293,8 @@
|
||||
<string name="sk_instance_info_unavailable">Сервер тимчасово недоступний</string>
|
||||
<string name="sk_external_share_or_open_title">Поділитися або відкрити за допомогою облікового запису</string>
|
||||
<string name="sk_open_in_app_failed">Не вдалося відкрити в застосунку</string>
|
||||
<string name="sk_no_remote_info_hint">віддалена інформація недоступна</string>
|
||||
<string name="sk_settings_allow_remote_loading">Завантажити інформацію з віддалених серверів</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Спробуйте отримати точніші списки підписників, вподобань і поширень, завантаживши інформацію з джерела.</string>
|
||||
<string name="sk_error_loading_profile">Не вдалося завантажити профіль на ваш домашній сервер.</string>
|
||||
</resources>
|
||||
@@ -260,7 +260,7 @@
|
||||
<string name="sk_new_reports">New reports</string>
|
||||
<string name="sk_settings_server_version">Server version: %s</string>
|
||||
<string name="sk_notify_poll_results">Poll results</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Prefix reply CW with “re:”</string>
|
||||
<string name="sk_settings_prefix_reply_cw_with_re">Prefix CW with “re:” on replies to</string>
|
||||
<string name="sk_filtered">Filtered: %s</string>
|
||||
<string name="sk_expand">Expand</string>
|
||||
<string name="sk_collapse">Collapse</string>
|
||||
@@ -297,9 +297,12 @@
|
||||
<string name="sk_error_loading_profile">Failed loading the profile via %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Load info from remote instances</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Try fetching more accurate listings for followers, likes and boosts by loading the information from the instance of origin.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Reveal equal CWs in threads</string>
|
||||
<string name="sk_settings_auto_reveal_never">Never</string>
|
||||
<string name="sk_settings_auto_reveal_threads">Same author</string>
|
||||
<string name="sk_settings_auto_reveal_discussions">Discussions</string>
|
||||
<string name="sk_settings_auto_reveal_always">Always</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Reveal same CWs in replies from</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">nobody</string>
|
||||
<string name="sk_settings_auto_reveal_author">author</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">everyone</string>
|
||||
<string name="sk_settings_prefix_replies_always">everyone</string>
|
||||
<string name="sk_settings_prefix_replies_never">nobody</string>
|
||||
<string name="sk_settings_prefix_replies_to_others">others</string>
|
||||
<string name="sk_settings_forward_report_default">“Forward report” switch default</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user