diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java index e4e54b451..f2ad32cab 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java @@ -19,7 +19,6 @@ import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Notification; -import org.joinmastodon.android.model.PaginatedResponse; import org.joinmastodon.android.model.SearchResult; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.utils.StatusFilterPredicate; @@ -160,7 +159,7 @@ public class CacheController{ } } Instance instance=AccountSessionManager.getInstance().getInstanceInfo(accountSession.domain); - new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.pleroma != null) + new GetNotifications(maxID, count, onlyPosts ? EnumSet.of(Notification.Type.STATUS) : onlyMentions ? EnumSet.of(Notification.Type.MENTION): EnumSet.allOf(Notification.Type.class), instance.isPleroma()) .setCallback(new Callback<>(){ @Override public void onSuccess(List result){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/PleromaMarkNotificationsRead.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/PleromaMarkNotificationsRead.java new file mode 100644 index 000000000..6e9fe23e7 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/notifications/PleromaMarkNotificationsRead.java @@ -0,0 +1,30 @@ +package org.joinmastodon.android.api.requests.notifications; + +import android.text.TextUtils; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Notification; + +import java.util.List; + +import okhttp3.MultipartBody; +import okhttp3.RequestBody; + +public class PleromaMarkNotificationsRead extends MastodonAPIRequest> { + private String maxID; + public PleromaMarkNotificationsRead(String maxID) { + super(HttpMethod.POST, "/pleroma/notifications/read", new TypeToken<>(){}); + this.maxID = maxID; + } + + @Override + public RequestBody getRequestBody() { + MultipartBody.Builder builder=new MultipartBody.Builder() + .setType(MultipartBody.FORM); + if(!TextUtils.isEmpty(maxID)) + builder.addFormDataPart("max_id", maxID); + return builder.build(); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetBubbleTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetBubbleTimeline.java new file mode 100644 index 000000000..9b54d1895 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetBubbleTimeline.java @@ -0,0 +1,23 @@ +package org.joinmastodon.android.api.requests.timelines; + +import android.text.TextUtils; + +import com.google.gson.reflect.TypeToken; + +import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.Status; + +import java.util.List; + +public class GetBubbleTimeline extends MastodonAPIRequest> { + public GetBubbleTimeline(String maxID, int limit) { + super(HttpMethod.GET, "/timelines/bubble", new TypeToken<>(){}); + if(!TextUtils.isEmpty(maxID)) + addQueryParameter("max_id", maxID); + if(limit>0) + addQueryParameter("limit", limit+""); + if(GlobalUserPreferences.replyVisibility != null) + addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java index 4a3c831df..1670c38c2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetHashtagTimeline.java @@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines; import com.google.gson.reflect.TypeToken; +import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.model.Status; @@ -16,5 +17,7 @@ public class GetHashtagTimeline extends MastodonAPIRequest>{ addQueryParameter("min_id", minID); if(limit>0) addQueryParameter("limit", ""+limit); + if(GlobalUserPreferences.replyVisibility != null) + addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java index 145a740bc..82d537971 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetListTimeline.java @@ -2,6 +2,7 @@ package org.joinmastodon.android.api.requests.timelines; import com.google.gson.reflect.TypeToken; +import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.model.Status; @@ -18,5 +19,7 @@ public class GetListTimeline extends MastodonAPIRequest> { addQueryParameter("limit", ""+limit); if(sinceID!=null) addQueryParameter("since_id", sinceID); + if(GlobalUserPreferences.replyVisibility != null) + addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java index 6723c18b9..7ec562704 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/timelines/GetPublicTimeline.java @@ -4,6 +4,7 @@ import android.text.TextUtils; import com.google.gson.reflect.TypeToken; +import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.model.Status; @@ -20,5 +21,7 @@ public class GetPublicTimeline extends MastodonAPIRequest>{ addQueryParameter("max_id", maxID); if(limit>0) addQueryParameter("limit", limit+""); + if(GlobalUserPreferences.replyVisibility != null) + addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java index da32d9d1a..d94e11d41 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java @@ -7,6 +7,7 @@ import org.joinmastodon.android.api.StatusInteractionController; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Application; import org.joinmastodon.android.model.Filter; +import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Markers; import org.joinmastodon.android.model.Preferences; import org.joinmastodon.android.model.PushSubscription; @@ -87,4 +88,8 @@ public class AccountSession{ pushSubscriptionManager=new PushSubscriptionManager(getID()); return pushSubscriptionManager; } + + public Instance getInstance() { + return AccountSessionManager.getInstance().getInstanceInfo(domain); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java index 445e573c6..1a174099a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java @@ -1087,7 +1087,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr } req.status=text; req.localOnly=localOnly; - req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility; + req.visibility=localOnly && instance.isPleroma() ? StatusPrivacy.LOCAL : statusVisibility; req.sensitive=sensitive; req.language=language; req.contentType=contentType; @@ -1743,11 +1743,24 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr pollChanged=true; updatePublishButtonState(); })); - option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0 ? instance.configuration.polls.maxCharactersPerOption : 50)}); + + int maxCharactersPerOption = 50; + if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxCharactersPerOption>0) + maxCharactersPerOption = instance.configuration.polls.maxCharactersPerOption; + else if(instance.pollLimits!=null && instance.pollLimits.maxOptionChars>0) + maxCharactersPerOption = instance.pollLimits.maxOptionChars; + option.edit.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxCharactersPerOption)}); pollOptionsView.addView(option.view); pollOptions.add(option); - if(pollOptions.size()==(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0 ? instance.configuration.polls.maxOptions : 4)) + + int maxPollOptions = 4; + if(instance.configuration!=null && instance.configuration.polls!=null && instance.configuration.polls.maxOptions>0) + maxPollOptions = instance.configuration.polls.maxOptions; + else if (instance.pollLimits!=null && instance.pollLimits.maxOptions>0) + maxPollOptions = instance.pollLimits.maxOptions; + + if(pollOptions.size()==maxPollOptions) addPollOptionBtn.setVisibility(View.GONE); return option; } @@ -1889,7 +1902,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr Menu m=visibilityPopup.getMenu(); MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only); boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID); - if (instance.pleroma != null) { + if (instance.isPleroma()) { m.findItem(R.id.vis_local).setVisible(true); } else if (localOnly || prefsSaysSupported) { localOnlyItem.setVisible(true); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java index 3dbf2f2c9..784d7920a 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java @@ -30,8 +30,11 @@ import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.lists.GetLists; import org.joinmastodon.android.api.requests.tags.GetFollowedHashtags; +import org.joinmastodon.android.api.session.AccountSession; +import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.HeaderPaginationList; +import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.TimelineDefinition; import org.joinmastodon.android.ui.DividerItemDecoration; @@ -164,7 +167,7 @@ public class EditTimelinesFragment extends RecyclerFragment makeBackItem(listsMenu); makeBackItem(hashtagsMenu); - TimelineDefinition.ALL_TIMELINES.forEach(tl -> addTimelineToOptions(tl, timelinesMenu)); + TimelineDefinition.getAllTimelines(accountID).forEach(tl -> addTimelineToOptions(tl, timelinesMenu)); listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu)); hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu)); @@ -190,7 +193,7 @@ public class EditTimelinesFragment extends RecyclerFragment @Override protected void doLoadData(int offset, int count){ - onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES), false); + onDataLoaded(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)), false); updateOptionsMenu(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index 6fa71d645..272692e95 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -83,6 +83,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene homeTabFragment=new HomeTabFragment(); homeTabFragment.setArguments(args); args=new Bundle(args); + Instance instance = AccountSessionManager.getInstance().getAccount(accountID).getInstance(); + args.putBoolean("isPleroma", instance.isPleroma()); args.putBoolean("noAutoLoad", true); searchFragment=new DiscoverFragment(); searchFragment.setArguments(args); @@ -231,6 +233,8 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene if (newFragment instanceof HasFab fabulous) fabulous.showFab(); currentTab=tab; ((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this); + if (tab == R.id.tab_search) + searchFragment.selectSearch(); } private void maybeTriggerLoading(Fragment newFragment){ @@ -290,10 +294,10 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene public void updateNotificationBadge() { AccountSession session = AccountSessionManager.getInstance().getAccount(accountID); - Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain); + Instance instance = session.getInstance(); if (instance == null) return; - new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.pleroma != null) + new GetNotifications(null, 1, EnumSet.allOf(Notification.Type.class), instance != null && instance.isPleroma()) .setCallback(new Callback<>() { @Override public void onSuccess(List notifications) { diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java index 0fba783f5..3616eb5e4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java @@ -106,7 +106,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab super.onCreate(savedInstanceState); E.register(this); accountID = getArguments().getString("account"); - timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES); + timelineDefinitions = GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)); assert timelineDefinitions != null; if (timelineDefinitions.size() == 0) timelineDefinitions = List.of(TimelineDefinition.HOME_TIMELINE); count = timelineDefinitions.size(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index 18915c7e1..ba30cf5a6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -9,7 +9,10 @@ import com.squareup.otto.Subscribe; import org.joinmastodon.android.E; import org.joinmastodon.android.R; +import org.joinmastodon.android.api.MastodonErrorResponse; import org.joinmastodon.android.api.requests.markers.SaveMarkers; +import org.joinmastodon.android.api.requests.notifications.PleromaMarkNotificationsRead; +import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.AllNotificationsSeenEvent; import org.joinmastodon.android.events.PollUpdatedEvent; @@ -18,6 +21,7 @@ import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.CacheablePaginatedResponse; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Filter; +import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.AccountCardStatusDisplayItem; @@ -40,6 +44,7 @@ import java.util.stream.Stream; import androidx.recyclerview.widget.RecyclerView; import me.grishka.appkit.Nav; +import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.SimpleCallback; public class NotificationsListFragment extends BaseStatusListFragment{ @@ -158,6 +163,9 @@ public class NotificationsListFragment extends BaseStatusListFragment(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES)); + pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID))); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index a6fe0b022..174d3d19b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -31,7 +31,6 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.inputmethod.InputMethodManager; -import android.view.animation.TranslateAnimation; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; @@ -51,6 +50,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; import org.joinmastodon.android.api.requests.accounts.GetOwnAccount; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; import org.joinmastodon.android.api.requests.accounts.UpdateAccountCredentials; +import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.account_list.FollowerListFragment; import org.joinmastodon.android.fragments.account_list.FollowingListFragment; @@ -58,6 +58,7 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.AccountField; import org.joinmastodon.android.model.Attachment; +import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.ui.BetterItemAnimator; import org.joinmastodon.android.ui.SimpleViewHolder; @@ -137,6 +138,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList private Account account; private String accountID; + private String domain; private Relationship relationship; private int statusBarHeight; private boolean isOwnProfile; @@ -151,7 +153,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList private PhotoViewer currentPhotoViewer; private boolean editModeLoading; - private static final int MAX_FIELDS=4; + private int maxFields = 4; // from ProfileAboutFragment public UsableRecyclerView list; @@ -172,6 +174,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList setRetainInstance(true); accountID=getArguments().getString("account"); + domain=AccountSessionManager.getInstance().getAccount(accountID).domain; if(getArguments().containsKey("profileAccount")){ account=Parcels.unwrap(getArguments().getParcelable("profileAccount")); profileAccountID=account.id; @@ -179,6 +182,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList loaded=true; if(!isOwnProfile) loadRelationship(); + else { + Instance instance = AccountSessionManager.getInstance().getInstanceInfo(domain); + if (instance.isPleroma()) { + maxFields = instance.pleroma.metadata.fieldsLimits.maxFields; + } + } }else{ profileAccountID=getArguments().getString("profileAccountID"); if(!getArguments().getBoolean("noAutoLoad", false)) @@ -324,7 +333,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList username.setOnLongClickListener(v->{ String usernameString=account.acct; if(!usernameString.contains("@")){ - usernameString+="@"+AccountSessionManager.getInstance().getAccount(accountID).domain; + usernameString+="@"+domain; } UiUtils.copyText(username, '@'+usernameString); return true; @@ -510,7 +519,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList ssb.append(account.acct); if(isSelf){ ssb.append('@'); - ssb.append(AccountSessionManager.getInstance().getAccount(accountID).domain); + ssb.append(domain); } ssb.append(" "); Drawable lock=username.getResources().getDrawable(R.drawable.ic_lock, getActivity().getTheme()).mutate(); @@ -520,7 +529,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList username.setText(ssb); }else{ // noinspection SetTextI18n - username.setText('@'+account.acct+(isSelf ? ('@'+AccountSessionManager.getInstance().getAccount(accountID).domain) : "")); + username.setText('@'+account.acct+(isSelf ? ('@'+domain) : "")); } CharSequence parsedBio=HtmlParser.parse(account.note, account.emojis, Collections.emptyList(), Collections.emptyList(), accountID); if(TextUtils.isEmpty(parsedBio)){ @@ -1189,7 +1198,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList public int getItemCount(){ if(isInEditMode){ int size=metadataListData.size(); - if(size{ PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL); popupMenu.inflate(R.menu.reply_visibility); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java index 627e7a0ad..9a4ded773 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java @@ -74,7 +74,7 @@ public class ThreadFragment extends StatusListFragment{ } AccountSession account=AccountSessionManager.getInstance().getAccount(accountID); Instance instance=AccountSessionManager.getInstance().getInstanceInfo(account.domain); - if(instance.pleroma != null){ + if(instance.isPleroma()){ List threadIds=new ArrayList<>(); threadIds.add(mainStatus.id); for(Status s:result.descendants){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/BubbleTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/BubbleTimelineFragment.java new file mode 100644 index 000000000..22b746bbc --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/BubbleTimelineFragment.java @@ -0,0 +1,55 @@ +package org.joinmastodon.android.fragments.discover; + +import android.os.Bundle; +import android.view.View; + +import org.joinmastodon.android.api.requests.timelines.GetBubbleTimeline; +import org.joinmastodon.android.api.requests.timelines.GetPublicTimeline; +import org.joinmastodon.android.fragments.StatusListFragment; +import org.joinmastodon.android.model.Filter; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper; +import org.joinmastodon.android.utils.StatusFilterPredicate; + +import java.util.List; +import java.util.stream.Collectors; + +import me.grishka.appkit.api.SimpleCallback; + +public class BubbleTimelineFragment extends StatusListFragment { + private DiscoverInfoBannerHelper bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.BUBBLE_TIMELINE); + private String maxID; + + @Override + protected boolean wantsComposeButton() { + return true; + } + + + @Override + protected void doLoadData(int offset, int count){ + currentRequest=new GetBubbleTimeline(refreshing ? null : maxID, count) + .setCallback(new SimpleCallback<>(this){ + @Override + public void onSuccess(List result){ + if(!result.isEmpty()) + maxID=result.get(result.size()-1).id; + if (getActivity() == null) return; + result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList()); + onDataLoaded(result, !result.isEmpty()); + } + }) + .exec(accountID); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + super.onViewCreated(view, savedInstanceState); + bannerHelper.maybeAddBanner(contentWrap); + } + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.PUBLIC; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java index 67f7b7a69..0fb383eca 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverFragment.java @@ -19,6 +19,7 @@ import android.widget.TextView; import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; +import org.joinmastodon.android.fragments.HomeFragment; import org.joinmastodon.android.fragments.IsOnTop; import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.ui.SimpleViewHolder; @@ -238,7 +239,7 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, else scrollToTop(); } - private void selectSearch() { + public void selectSearch() { searchEdit.requestFocus(); onSearchEditFocusChanged(searchEdit, true); getActivity().getSystemService(InputMethodManager.class).showSoftInput(searchEdit, 0); @@ -272,6 +273,8 @@ public class DiscoverFragment extends AppKitFragment implements ScrollableToTop, searchBack.setEnabled(false); searchBack.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(searchEdit.getWindowToken(), 0); + if (getArguments().getBoolean("isPleroma")) + ((HomeFragment) getParentFragment()).onBackPressed(); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java index bf0b9e4c8..f5063456b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/TrendingHashtagsFragment.java @@ -1,5 +1,8 @@ package org.joinmastodon.android.fragments.discover; +import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withHistoryParams; +import static org.joinmastodon.android.ui.displayitems.HashtagStatusDisplayItem.Holder.withoutHistoryParams; + import android.os.Bundle; import android.view.View; import android.view.ViewGroup; @@ -108,9 +111,11 @@ public class TrendingHashtagsFragment extends RecyclerFragment implemen if (item.history == null || item.history.isEmpty()) { subtitle.setText(null); chart.setVisibility(View.GONE); + title.setLayoutParams(withoutHistoryParams); return; } chart.setVisibility(View.VISIBLE); + title.setLayoutParams(withHistoryParams); int numPeople=item.history.get(0).accounts; if(item.history.size()>1) numPeople+=item.history.get(1).accounts; diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java index 09aae3adc..cd8e94d99 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java @@ -86,6 +86,8 @@ public class Instance extends BaseModel{ public Pleroma pleroma; + public PleromaPollLimits pollLimits; + @Override public void postprocess() throws ObjectValidationException{ super.postprocess(); @@ -134,6 +136,10 @@ public class Instance extends BaseModel{ return ci; } + public boolean isPleroma() { + return pleroma != null; + } + @Parcel public static class Rule{ public String id; @@ -198,6 +204,28 @@ public class Instance extends BaseModel{ @Parcel public static class Pleroma extends BaseModel { - // metadata etc + public Pleroma.Metadata metadata; + + @Parcel + public static class Metadata { + public List features; + public Pleroma.Metadata.FieldsLimits fieldsLimits; + + @Parcel + public static class FieldsLimits { + public int maxFields; + public int maxRemoteFields; + public int nameLength; + public int valueLength; + } + } + } + + @Parcel + public static class PleromaPollLimits { + public int maxExpiration; + public int maxOptionChars; + public int maxOptions; + public int minExpiration; } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java index fd7c3346a..bff0046b3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java @@ -8,17 +8,20 @@ import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.R; +import org.joinmastodon.android.api.session.AccountSession; +import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.HashtagTimelineFragment; import org.joinmastodon.android.fragments.HomeTimelineFragment; import org.joinmastodon.android.fragments.ListTimelineFragment; import org.joinmastodon.android.fragments.NotificationsListFragment; +import org.joinmastodon.android.fragments.discover.BubbleTimelineFragment; import org.joinmastodon.android.fragments.discover.FederatedTimelineFragment; import org.joinmastodon.android.fragments.discover.LocalTimelineFragment; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; public class TimelineDefinition { private TimelineType type; @@ -58,6 +61,14 @@ public class TimelineDefinition { this.type = type; } + public boolean isCompatible(AccountSession session) { + return true; + } + + public boolean wantsDefault(AccountSession session) { + return true; + } + public String getTitle(Context ctx) { return title != null ? title : getDefaultTitle(ctx); } @@ -78,6 +89,7 @@ public class TimelineDefinition { case POST_NOTIFICATIONS -> ctx.getString(R.string.sk_timeline_posts); case LIST -> listTitle; case HASHTAG -> hashtagName; + case BUBBLE -> ctx.getString(R.string.sk_timeline_bubble); }; } @@ -89,6 +101,7 @@ public class TimelineDefinition { case POST_NOTIFICATIONS -> Icon.POST_NOTIFICATIONS; case LIST -> Icon.LIST; case HASHTAG -> Icon.HASHTAG; + case BUBBLE -> Icon.BUBBLE; }; } @@ -100,6 +113,7 @@ public class TimelineDefinition { case LIST -> new ListTimelineFragment(); case HASHTAG -> new HashtagTimelineFragment(); case POST_NOTIFICATIONS -> new NotificationsListFragment(); + case BUBBLE -> new BubbleTimelineFragment(); }; } @@ -156,7 +170,7 @@ public class TimelineDefinition { return args; } - public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG } + public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG, BUBBLE } public enum Icon { HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart), @@ -219,7 +233,8 @@ public class TimelineDefinition { FEDERATED(R.drawable.ic_fluent_earth_24_regular, R.string.sk_timeline_federated, true), POST_NOTIFICATIONS(R.drawable.ic_fluent_chat_24_regular, R.string.sk_timeline_posts, true), LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true), - HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true); + HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true), + BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true); public final int iconRes, nameRes; public final boolean hidden; @@ -239,14 +254,49 @@ public class TimelineDefinition { public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL); public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED); public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS); + public static final TimelineDefinition BUBBLE_TIMELINE = new TimelineDefinition(TimelineType.BUBBLE) { + @Override + public boolean isCompatible(AccountSession session) { + // still enabling the bubble timeline for all pleroma/akkoma instances since i know of + // at least one instance that supports it, but doesn't list "bubble_timeline" + return session.getInstance().isPleroma(); + } - public static final List DEFAULT_TIMELINES = BuildConfig.BUILD_TYPE.equals("playRelease") - ? List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy()) - : List.of(HOME_TIMELINE.copy(), LOCAL_TIMELINE.copy(), FEDERATED_TIMELINE.copy()); - public static final List ALL_TIMELINES = List.of( - HOME_TIMELINE.copy(), - LOCAL_TIMELINE.copy(), - FEDERATED_TIMELINE.copy(), - POSTS_TIMELINE.copy() + @Override + public boolean wantsDefault(AccountSession session) { + Instance instance = session.getInstance(); + return instance.isPleroma() && instance.pleroma.metadata.features.contains("bubble_timeline"); + } + }; + + public static List getDefaultTimelines(String accountId) { + AccountSession session = AccountSessionManager.getInstance().getAccount(accountId); + return DEFAULT_TIMELINES.stream() + .filter(tl -> tl.isCompatible(session) && tl.wantsDefault(session)) + .map(TimelineDefinition::copy) + .collect(Collectors.toList()); + } + + public static List getAllTimelines(String accountId) { + AccountSession session = AccountSessionManager.getInstance().getAccount(accountId); + return ALL_TIMELINES.stream() + .filter(tl -> tl.isCompatible(session)) + .map(TimelineDefinition::copy) + .collect(Collectors.toList()); + } + + private static final List DEFAULT_TIMELINES = List.of( + HOME_TIMELINE, + LOCAL_TIMELINE, + BUBBLE_TIMELINE, + FEDERATED_TIMELINE + ); + + private static final List ALL_TIMELINES = List.of( + HOME_TIMELINE, + LOCAL_TIMELINE, + FEDERATED_TIMELINE, + POSTS_TIMELINE, + BUBBLE_TIMELINE ); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java index 586fb45a9..e337083b0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HashtagStatusDisplayItem.java @@ -3,6 +3,7 @@ package org.joinmastodon.android.ui.displayitems; import android.content.Context; import android.view.View; import android.view.ViewGroup; +import android.widget.RelativeLayout; import android.widget.TextView; import org.joinmastodon.android.R; @@ -26,6 +27,13 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder{ private final TextView title, subtitle; private final HashtagChartView chart; + public static final RelativeLayout.LayoutParams + withHistoryParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT), + withoutHistoryParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + static { + withoutHistoryParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + } public Holder(Context context, ViewGroup parent){ super(context, R.layout.item_trending_hashtag, parent); @@ -41,9 +49,11 @@ public class HashtagStatusDisplayItem extends StatusDisplayItem{ if (item.history == null || item.history.isEmpty()) { subtitle.setText(null); chart.setVisibility(View.GONE); + title.setLayoutParams(withoutHistoryParams); return; } chart.setVisibility(View.VISIBLE); + title.setLayoutParams(withHistoryParams); int numPeople=item.history.get(0).accounts; if(item.history.size()>1) numPeople+=item.history.get(1).accounts; diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 99de1e3b7..be3b0a779 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -166,6 +166,15 @@ public abstract class StatusDisplayItem{ items.add(replyLine); } + if (statusForContent.quote != null) { + boolean hasQuoteInlineTag = statusForContent.content.contains(""); + if (!hasQuoteInlineTag) { + String quoteUrl = statusForContent.quote.url; + String quoteInline = String.format("%sRE: %s", + statusForContent.content.endsWith("

") ? "" : "

", quoteUrl, quoteUrl); + statusForContent.content += quoteInline; + } + } if(!TextUtils.isEmpty(statusForContent.content)) items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate)); else if (!GlobalUserPreferences.replyLineAboveHeader && replyLine != null) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java index edc114101..6c2d01d56 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/DiscoverInfoBannerHelper.java @@ -38,6 +38,7 @@ public class DiscoverInfoBannerHelper{ case LOCAL_TIMELINE -> R.string.local_timeline_info_banner; case FEDERATED_TIMELINE -> R.string.sk_federated_timeline_info_banner; case POST_NOTIFICATIONS -> R.string.sk_notify_posts_info_banner; + case BUBBLE_TIMELINE -> R.string.sk_bubble_timeline_info_banner; }); } } @@ -63,6 +64,7 @@ public class DiscoverInfoBannerHelper{ LOCAL_TIMELINE, FEDERATED_TIMELINE, POST_NOTIFICATIONS, -// ACCOUNTS +// ACCOUNTS, + BUBBLE_TIMELINE } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 090e0b06b..0d39c0ad9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -99,6 +99,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -946,7 +947,7 @@ public class UiUtils { public static String getInstanceName(String accountID) { AccountSession session = AccountSessionManager.getInstance().getAccount(accountID); - Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain); + Instance instance = session.getInstance(); return instance != null && !instance.title.isBlank() ? instance.title : session.domain; } @@ -1112,14 +1113,20 @@ public class UiUtils { if (!results.statuses.isEmpty()) { args.putParcelable("status", Parcels.wrap(results.statuses.get(0))); Nav.go((Activity) context, ThreadFragment.class, args); - } else if (!results.accounts.isEmpty()) { - args.putParcelable("profileAccount", Parcels.wrap(results.accounts.get(0))); - Nav.go((Activity) context, ProfileFragment.class, args); - } else { - if (launchBrowser) launchWebBrowser(context, url); - else - Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); + return; } + Optional account = results.accounts.stream() + .filter(a -> uri.equals(Uri.parse(a.url))).findAny(); + if (account.isPresent()) { + args.putParcelable("profileAccount", Parcels.wrap(account.get())); + Nav.go((Activity) context, ProfileFragment.class, args); + return; + } + if (launchBrowser) { + launchWebBrowser(context, url); + return; + } + Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show(); } @Override diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index 6eae86635..06cf3eeb4 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -30,6 +30,7 @@ Turned off post notifications for %s Federation These are the most recent posts by the people in your federation. + These are the most recent posts by the people in your Akkoma server\'s bubble. Megalodon %s is ready to download. Megalodon %s is downloaded and ready to install. Check for update @@ -148,6 +149,7 @@ Home Local Federation + Bubble Type to start searching Remove as follower Remove %s as a follower by blocking and immediately unblocking them?