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:
@@ -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
Reference in New Issue
Block a user