More Akkoma improvements (#524)
* Only open account if domain matches Akkoma will seemingly show results that don't match well. This checks if the domain matches before continuing * Add "RE:" for quotes where it's missing * Fix no hashtag history in search * Skip not implemented discovery and select search on Pleroma * Set proper max account fields for Pleroma * Use Pleroma's non-standard poll limits * Mark notifications as read properly on Pleroma * Akkoma bubble timeline * Respect Reply Visibility preference on all timelines * vertically center if hashtag has no history * only open account search result if uri equals * add getInstance and isPleroma methods * change timelines api, support compatibility checks --------- Co-authored-by: sk <sk22@mailbox.org>
This commit is contained in:
parent
231f19d113
commit
b5f6687925
|
@ -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<Notification> result){
|
||||
|
|
|
@ -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<List<Notification>> {
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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<List<Status>> {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<List<Status>>{
|
|||
addQueryParameter("min_id", minID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", ""+limit);
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<List<Status>> {
|
|||
addQueryParameter("limit", ""+limit);
|
||||
if(sinceID!=null)
|
||||
addQueryParameter("since_id", sinceID);
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<List<Status>>{
|
|||
addQueryParameter("max_id", maxID);
|
||||
if(limit>0)
|
||||
addQueryParameter("limit", limit+"");
|
||||
if(GlobalUserPreferences.replyVisibility != null)
|
||||
addQueryParameter("reply_visibility", GlobalUserPreferences.replyVisibility);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<TimelineDefinition>
|
|||
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<TimelineDefinition>
|
|||
|
||||
@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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Notification> notifications) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Notification>{
|
||||
|
@ -158,6 +163,9 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
|
|||
AccountSessionManager.getInstance().getAccount(accountID).markers
|
||||
.notifications.lastReadId = result.items.get(0).id;
|
||||
AccountSessionManager.getInstance().writeAccountsFile();
|
||||
|
||||
if (AccountSessionManager.getInstance().getAccount(accountID).getInstance().isPleroma())
|
||||
new PleromaMarkNotificationsRead(result.items.get(0).id).exec(accountID);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ public abstract class PinnableStatusListFragment extends StatusListFragment {
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.DEFAULT_TIMELINES));
|
||||
pinnedTimelines = new ArrayList<>(GlobalUserPreferences.pinnedTimelines.getOrDefault(accountID, TimelineDefinition.getDefaultTimelines(accountID)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<MAX_FIELDS)
|
||||
if(size<maxFields)
|
||||
size++;
|
||||
return size;
|
||||
}
|
||||
|
@ -1306,7 +1315,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
|||
@Override
|
||||
public void onClick(){
|
||||
metadataListData.add(new AccountField());
|
||||
if(metadataListData.size()==MAX_FIELDS){ // replace this row with new row
|
||||
if(metadataListData.size()==maxFields){ // replace this row with new row
|
||||
adapter.notifyItemChanged(metadataListData.size()-1);
|
||||
}else{
|
||||
adapter.notifyItemInserted(metadataListData.size()-1);
|
||||
|
|
|
@ -105,7 +105,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||
imageCache = ImageCache.getInstance(getActivity());
|
||||
accountID=getArguments().getString("account");
|
||||
AccountSession session=AccountSessionManager.getInstance().getAccount(accountID);
|
||||
Instance instance = AccountSessionManager.getInstance().getInstanceInfo(session.domain);
|
||||
Instance instance = session.getInstance();
|
||||
String instanceName = UiUtils.getInstanceName(accountID);
|
||||
|
||||
if(GithubSelfUpdater.needSelfUpdating()){
|
||||
|
@ -223,7 +223,7 @@ public class SettingsFragment extends MastodonToolbarFragment{
|
|||
GlobalUserPreferences.showReplies=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
if (instance.pleroma != null) {
|
||||
if (instance.isPleroma()) {
|
||||
items.add(new ButtonItem(R.string.sk_settings_reply_visibility, R.drawable.ic_fluent_chat_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.reply_visibility);
|
||||
|
|
|
@ -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<String> threadIds=new ArrayList<>();
|
||||
threadIds.add(mainStatus.id);
|
||||
for(Status s:result.descendants){
|
||||
|
|
|
@ -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<Status> 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<Hashtag> 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;
|
||||
|
|
|
@ -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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TimelineDefinition> 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<TimelineDefinition> 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<TimelineDefinition> 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<TimelineDefinition> 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<TimelineDefinition> DEFAULT_TIMELINES = List.of(
|
||||
HOME_TIMELINE,
|
||||
LOCAL_TIMELINE,
|
||||
BUBBLE_TIMELINE,
|
||||
FEDERATED_TIMELINE
|
||||
);
|
||||
|
||||
private static final List<TimelineDefinition> ALL_TIMELINES = List.of(
|
||||
HOME_TIMELINE,
|
||||
LOCAL_TIMELINE,
|
||||
FEDERATED_TIMELINE,
|
||||
POSTS_TIMELINE,
|
||||
BUBBLE_TIMELINE
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<HashtagStatusDisplayItem>{
|
||||
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;
|
||||
|
|
|
@ -166,6 +166,15 @@ public abstract class StatusDisplayItem{
|
|||
items.add(replyLine);
|
||||
}
|
||||
|
||||
if (statusForContent.quote != null) {
|
||||
boolean hasQuoteInlineTag = statusForContent.content.contains("<span class=\"quote-inline\">");
|
||||
if (!hasQuoteInlineTag) {
|
||||
String quoteUrl = statusForContent.quote.url;
|
||||
String quoteInline = String.format("<span class=\"quote-inline\">%sRE: <a href=\"%s\">%s</a></span>",
|
||||
statusForContent.content.endsWith("</p>") ? "" : "<br/><br/>", 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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> 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
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<string name="sk_user_post_notifications_off">Turned off post notifications for %s</string>
|
||||
<string name="sk_federated_timeline">Federation</string>
|
||||
<string name="sk_federated_timeline_info_banner">These are the most recent posts by the people in your federation.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">These are the most recent posts by the people in your Akkoma server\'s bubble.</string>
|
||||
<string name="sk_update_available">Megalodon %s is ready to download.</string>
|
||||
<string name="sk_update_ready">Megalodon %s is downloaded and ready to install.</string>
|
||||
<string name="sk_check_for_update">Check for update</string>
|
||||
|
@ -148,6 +149,7 @@
|
|||
<string name="sk_timeline_home">Home</string>
|
||||
<string name="sk_timeline_local">Local</string>
|
||||
<string name="sk_timeline_federated">Federation</string>
|
||||
<string name="sk_timeline_bubble">Bubble</string>
|
||||
<string name="sk_recent_searches_placeholder">Type to start searching</string>
|
||||
<string name="sk_remove_follower">Remove as follower</string>
|
||||
<string name="sk_remove_follower_confirm">Remove %s as a follower by blocking and immediately unblocking them?</string>
|
||||
|
|
Loading…
Reference in New Issue