Merge remote-tracking branch 'megalodon_main/main'

# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FileStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
#	mastodon/src/main/res/menu/profile.xml
#	metadata/cy/full_description.txt
This commit is contained in:
LucasGGamerM
2023-10-18 08:21:03 -03:00
51 changed files with 579 additions and 406 deletions

View File

@@ -54,7 +54,6 @@ public class GlobalUserPreferences{
public static boolean collapseLongPosts;
public static boolean spectatorMode;
public static boolean autoHideFab;
public static boolean compactReblogReplyLine;
public static boolean allowRemoteLoading;
public static boolean forwardReportDefault;
public static AutoRevealMode autoRevealEqualSpoilers;
@@ -130,7 +129,6 @@ public class GlobalUserPreferences{
collapseLongPosts=prefs.getBoolean("collapseLongPosts", true);
spectatorMode=prefs.getBoolean("spectatorMode", false);
autoHideFab=prefs.getBoolean("autoHideFab", true);
compactReblogReplyLine=prefs.getBoolean("compactReblogReplyLine", true);
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
forwardReportDefault=prefs.getBoolean("forwardReportDefault", true);
@@ -208,7 +206,6 @@ public class GlobalUserPreferences{
.putBoolean("collapseLongPosts", collapseLongPosts)
.putBoolean("spectatorMode", spectatorMode)
.putBoolean("autoHideFab", autoHideFab)
.putBoolean("compactReblogReplyLine", compactReblogReplyLine)
.putBoolean("allowRemoteLoading", allowRemoteLoading)
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
.putBoolean("forwardReportDefault", forwardReportDefault)

View File

@@ -8,7 +8,10 @@ import org.joinmastodon.android.api.requests.statuses.SetStatusBookmarked;
import org.joinmastodon.android.api.requests.statuses.SetStatusFavorited;
import org.joinmastodon.android.api.requests.statuses.SetStatusMuted;
import org.joinmastodon.android.api.requests.statuses.SetStatusReblogged;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.StatusPrivacy;
@@ -50,7 +53,7 @@ public class StatusInteractionController{
runningFavoriteRequests.remove(status.id);
result.favouritesCount = Math.max(0, status.favouritesCount + (favorited ? 1 : -1));
cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}
@Override
@@ -59,13 +62,13 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context);
status.favourited=!favorited;
cb.accept(status);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningFavoriteRequests.put(status.id, req);
status.favourited=favorited;
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
public void setReblogged(Status status, boolean reblogged, StatusPrivacy visibility, Consumer<Status> cb){
@@ -80,11 +83,15 @@ public class StatusInteractionController{
.setCallback(new Callback<>(){
@Override
public void onSuccess(Status reblog){
Status result = reblog.getContentStatus();
Status result=reblog.getContentStatus();
runningReblogRequests.remove(status.id);
result.reblogsCount = Math.max(0, status.reblogsCount + (reblogged ? 1 : -1));
cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if(updateCounters){
E.post(new StatusCountersUpdatedEvent(result));
if(reblogged) E.post(new StatusCreatedEvent(reblog, accountID));
else E.post(new ReblogDeletedEvent(status.id, accountID));
}
}
@Override
@@ -93,13 +100,13 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context);
status.reblogged=!reblogged;
cb.accept(status);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningReblogRequests.put(status.id, req);
status.reblogged=reblogged;
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
public void setBookmarked(Status status, boolean bookmarked){
@@ -120,7 +127,7 @@ public class StatusInteractionController{
public void onSuccess(Status result){
runningBookmarkRequests.remove(status.id);
cb.accept(result);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(result));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(result));
}
@Override
@@ -129,12 +136,12 @@ public class StatusInteractionController{
error.showToast(MastodonApp.context);
status.bookmarked=!bookmarked;
cb.accept(status);
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
})
.exec(accountID);
runningBookmarkRequests.put(status.id, req);
status.bookmarked=bookmarked;
if (updateCounters) E.post(new StatusCountersUpdatedEvent(status));
if(updateCounters) E.post(new StatusCountersUpdatedEvent(status));
}
}

View File

@@ -0,0 +1,11 @@
package org.joinmastodon.android.events;
public class ReblogDeletedEvent{
public final String statusID;
public final String accountID;
public ReblogDeletedEvent(String statusID, String accountID){
this.statusID=statusID;
this.accountID=accountID;
}
}

View File

@@ -1,7 +1,5 @@
package org.joinmastodon.android.events;
import org.joinmastodon.android.model.ScheduledStatus;
public class ScheduledStatusDeletedEvent{
public final String id;
public final String accountID;

View File

@@ -32,7 +32,6 @@ import android.text.TextWatcher;
import android.text.format.DateFormat;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -124,11 +123,11 @@ import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -166,7 +165,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
public LinearLayout mainLayout;
private SizeListenerLinearLayout contentView;
private TextView selfName, selfUsername, selfExtraText, extraText, pronouns;
private TextView selfName, selfUsername, selfExtraText, extraText;
private ImageView selfAvatar;
private Account self;
private String instanceDomain;
@@ -686,7 +685,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
});
View originalPost=view.findViewById(R.id.original_post);
extraText=view.findViewById(R.id.extra_text);
pronouns=view.findViewById(R.id.pronouns);
originalPost.setVisibility(View.VISIBLE);
originalPost.setOnClickListener(v->{
Bundle args=new Bundle();
@@ -863,6 +861,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
inflater.inflate(editingStatus==null ? R.menu.compose : R.menu.compose_edit, menu);
@@ -911,7 +910,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
languageButton = wrap.findViewById(R.id.language_btn);
languageButton.setOnClickListener(v->showLanguageAlert());
languageButton.setOnLongClickListener(v->{
languageButton.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
if(!getLocalPrefs().bottomEncoding){
getLocalPrefs().bottomEncoding=true;
getLocalPrefs().save();
@@ -919,22 +917,27 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
return false;
});
if(GlobalUserPreferences.relocatePublishButton){
publishButtonRelocated.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
checkAltTextsAndPublish();
else
publish();
});
} else {
publishButton.setOnClickListener(v -> {
if(GlobalUserPreferences.altTextReminders && editingStatus==null)
checkAltTextsAndPublish();
else
publish();
});
}
publishButton.setOnClickListener(v->{
Consumer<Boolean> draftCheckComplete=(isDraft)->{
if(GlobalUserPreferences.altTextReminders && !isDraft) checkAltTextsAndPublish();
else publish();
};
boolean isAlreadyDraft=scheduledAt!=null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT);
if(editingStatus!=null && scheduledAt!=null && isAlreadyDraft) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w)->draftCheckComplete.accept(isAlreadyDraft))
.setNegativeButton(R.string.publish, (d, w)->{
updateScheduledAt(null);
draftCheckComplete.accept(false);
})
.show();
}else{
draftCheckComplete.accept(isAlreadyDraft);
}
});
draftsBtn.setOnClickListener(v-> draftOptionsPopup.show());
draftsBtn.setOnTouchListener(draftOptionsPopup.getDragToOpenListener());
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
@@ -945,8 +948,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
? languageResolver.fromOrFallback(prefs.postingDefaultLanguage)
: languageResolver.getDefault());
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
if(isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
if(isInstancePixelfed() || (editingStatus!=null && !redraftStatus)) {
// editing an already published post
draftsBtn.setVisibility(View.GONE);
}
@@ -1077,7 +1080,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
@Override
protected int getNavigationIconDrawableResource(){
return R.drawable.ic_baseline_close_24;
return R.drawable.ic_fluent_dismiss_24_regular;
}
@Override
@@ -1146,31 +1149,23 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
overlayParams.token=mainEditText.getWindowToken();
wm.addView(sendingOverlay, overlayParams);
if(GlobalUserPreferences.relocatePublishButton){
publishButtonRelocated.setEnabled(false);
} else {
publishButton.setEnabled(false);
}
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false);
V.setVisibilityAnimated(sendProgress, View.VISIBLE);
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError);
}
private void actuallyPublish(){
actuallyPublish(false);
}
private void actuallyPublish(boolean force){
String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request();
if ("bottom".equals(postLang.encoding)) {
text = new StatusTextEncoder(Bottom::encode).encode(text);
req.spoilerText = "bottom-encoded emoji spam";
if("bottom".equals(postLang.encoding)){
text=new StatusTextEncoder(Bottom::encode).encode(text);
req.spoilerText="bottom-encoded emoji spam";
}
if (localOnly &&
if(localOnly &&
AccountSessionManager.get(accountID).getLocalPreferences().glitchInstance &&
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()) {
text += " " + GLITCH_LOCAL_ONLY_SUFFIX;
!GLITCH_LOCAL_ONLY_PATTERN.matcher(text).matches()){
text+=" "+GLITCH_LOCAL_ONLY_SUFFIX;
}
req.status=text;
req.localOnly=localOnly;
@@ -1184,19 +1179,6 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.mediaAttributes=mediaViewController.getAttachmentAttributes();
}
}
// ask whether to publish now when editing an existing draft
if (!force && editingStatus != null && scheduledAt != null && scheduledAt.isAfter(DRAFTS_AFTER_INSTANT)) {
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_save_draft)
.setMessage(R.string.sk_save_draft_message)
.setPositiveButton(R.string.save, (d, w) -> actuallyPublish(true))
.setNegativeButton(R.string.publish, (d, w) -> {
updateScheduledAt(null);
actuallyPublish();
})
.show();
return;
}
if(replyTo!=null || (editingStatus != null && editingStatus.inReplyToId!=null)){
req.inReplyToId=editingStatus!=null ? editingStatus.inReplyToId : replyTo.id;
}
@@ -1303,13 +1285,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
wm.removeView(sendingOverlay);
sendingOverlay=null;
V.setVisibilityAnimated(sendProgress, View.GONE);
if(GlobalUserPreferences.relocatePublishButton) {
publishButtonRelocated.setEnabled(true);
} else {
publishButton.setEnabled(true);
}
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(true);
if(error instanceof MastodonErrorResponse me){
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.post_failed)
@@ -1397,20 +1373,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void confirmDiscardDraftAndFinish(){
boolean attachmentsPending = mediaViewController.areAnyAttachmentsNotDone();
if (attachmentsPending) new M3AlertDialogBuilder(getActivity())
boolean attachmentsPending=mediaViewController.areAnyAttachmentsNotDone();
if(attachmentsPending) new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_unfinished_attachments)
.setMessage(R.string.sk_unfinished_attachments_message)
.setPositiveButton(R.string.edit, (d, w) -> {})
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
.setPositiveButton(R.string.ok, (d, w)->{})
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
.show();
else new M3AlertDialogBuilder(getActivity())
.setTitle(editingStatus != null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
.setPositiveButton(R.string.save, (d, w) -> {
updateScheduledAt(scheduledAt == null ? getDraftInstant() : scheduledAt);
.setTitle(editingStatus!=null ? R.string.sk_confirm_save_changes : R.string.sk_confirm_save_draft)
.setPositiveButton(R.string.save, (d, w)->{
updateScheduledAt(scheduledAt==null ? getDraftInstant() : scheduledAt);
publish();
})
.setNegativeButton(R.string.discard, (d, w) -> Nav.finish(this))
.setNegativeButton(R.string.discard, (d, w)->Nav.finish(this))
.show();
}
@@ -1615,8 +1591,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void updateHeaders() {
UiUtils.setExtraTextInfo(getContext(), selfExtraText, null, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, pronouns, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
UiUtils.setExtraTextInfo(getContext(), selfExtraText, false, false, localOnly || statusVisibility==StatusPrivacy.LOCAL, null);
if (replyTo != null) UiUtils.setExtraTextInfo(getContext(), extraText, true, false, replyTo.localOnly || replyTo.visibility==StatusPrivacy.LOCAL, replyTo.account);
}
private void buildVisibilityPopup(View v){

View File

@@ -271,7 +271,7 @@ public class FollowRequestsListFragment extends MastodonRecyclerFragment<FollowR
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);
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
if(relationship==null || !relationship.followedBy){
actionWrap.setVisibility(View.GONE);

View File

@@ -404,9 +404,8 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
addListsToOverflowMenu();
addHashtagsToOverflowMenu();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !UiUtils.isEMUI()) {
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
m.setGroupDividerEnabled(true);
}
}
@Override

View File

@@ -293,7 +293,7 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
continue;
Status contentStatus=ntf.status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(ntf.id, ntf.status, ev.poll);
updatePoll(ntf.id, contentStatus, ev.poll);
}
}
}

View File

@@ -846,20 +846,21 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
boolean hasMultipleAccounts = AccountSessionManager.getInstance().getLoggedInAccounts().size() > 1;
MenuItem openWithAccounts = menu.findItem(R.id.open_with_account);
openWithAccounts.setVisible(hasMultipleAccounts);
SubMenu accountsMenu = openWithAccounts.getSubMenu();
if (hasMultipleAccounts) {
SubMenu accountsMenu=openWithAccounts.getSubMenu();
if(hasMultipleAccounts){
accountsMenu.clear();
UiUtils.populateAccountsMenu(accountID, accountsMenu, s-> UiUtils.openURL(
getActivity(), s.getID(), account.url, false
));
}
menu.findItem(R.id.share).setTitle(R.string.share_user);
if(isOwnProfile) {
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
return;
}
MenuItem mute = menu.findItem(R.id.mute);
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
MenuItem mute=menu.findItem(R.id.mute);
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_2_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), mute);
@@ -867,19 +868,22 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
menu.findItem(R.id.report).setTitle(getString(R.string.report_user, account.getShortUsername()));
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
if (relationship.following) {
MenuItem hideBoosts = menu.findItem(R.id.hide_boosts);
hideBoosts.setTitle(getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
UiUtils.insetPopupMenuIcon(getContext(), hideBoosts);
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.sk_lists_with_user, account.getShortUsername()));
hideBoosts.setVisible(true);
} else {
menu.findItem(R.id.hide_boosts).setVisible(false);
hideBoosts.setVisible(false);
}
MenuItem blockDomain=menu.findItem(R.id.block_domain);
if(!account.isLocal()){
blockDomain.setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
blockDomain.setVisible(true);
}else{
blockDomain.setVisible(false);
}
if(!account.isLocal())
menu.findItem(R.id.block_domain).setTitle(getString(relationship.domainBlocking ? R.string.unblock_domain : R.string.block_domain, account.getDomain()));
else
menu.findItem(R.id.block_domain).setVisible(false);
}
@Override
@@ -891,11 +895,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
intent.putExtra(Intent.EXTRA_TEXT, account.url);
startActivity(Intent.createChooser(intent, item.getTitle()));
}else if(id==R.id.mute){
confirmToggleMuted();
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}else if(id==R.id.block){
confirmToggleBlocked();
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}else if(id==R.id.soft_block){
confirmSoftBlockUser();
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
}else if(id==R.id.report){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -1197,7 +1201,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
updateMetadataHeight();
Toolbar toolbar=getToolbar();
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_baseline_close_24).mutate();
Drawable close=getToolbarContext().getDrawable(R.drawable.ic_fluent_dismiss_24_regular).mutate();
close.setTint(UiUtils.getThemeColor(getToolbarContext(), R.attr.colorM3OnSurfaceVariant));
toolbar.setNavigationIcon(close);
toolbar.setNavigationContentDescription(R.string.discard);
@@ -1303,18 +1307,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
.exec(accountID);
}
private void confirmToggleMuted(){
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}
private void confirmToggleBlocked(){
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}
private void confirmSoftBlockUser(){
UiUtils.confirmSoftBlockUser(getActivity(), accountID, account, this::updateRelationship);
}
private void updateRelationship(Relationship r){
relationship=r;
updateRelationship();

View File

@@ -120,6 +120,15 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
}
}
@Override
protected void onShown(){
super.onShown();
// because, for some reason, when navigating back from compose fragment,
// match_parent would otherwise be incorrect (leaving a gap for the keyboard
// where there is none)
list.post(list::requestLayout);
}
@Override
protected void doLoadData(int offset, int count){
currentRequest=new GetScheduledStatuses(offset==0 ? null : nextMaxID, count)

View File

@@ -14,6 +14,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusMuteChangedEvent;
import org.joinmastodon.android.events.EmojiReactionsUpdatedEvent;
import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.events.ReblogDeletedEvent;
import org.joinmastodon.android.events.RemoveAccountPostsEvent;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
@@ -31,6 +32,7 @@ import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -159,12 +161,12 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
}
}
protected Status getContentStatusByID(String id){
public Status getContentStatusByID(String id){
Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus();
}
protected Status getStatusByID(String id){
public Status getStatusByID(String id){
for(Status s:data){
if(s.id.equals(id)){
return s;
@@ -191,43 +193,58 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
}
}
protected void removeStatus(Status status){
data.remove(status);
preloadedData.remove(status);
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
for(int i=0;i<displayItems.size();i++){
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;
private void iterateRemoveStatus(List<Status> l, String id){
Iterator<Status> it=l.iterator();
while(it.hasNext()){
if(it.next().getContentStatus().id.equals(id)){
it.remove();
}
}
}
private void removeStatusDisplayItems(Status status, int index, int ancestorFirstIndex, int ancestorLastIndex, boolean deleteContent){
// 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);
if(ancestorFirstIndex>=0 && ancestorLastIndex==index-1){
for(int i=ancestorFirstIndex; i<=ancestorLastIndex; i++){
StatusDisplayItem item=displayItems.get(i);
String id=deleteContent ? item.getContentID() : item.parentID;
// update ancestor to have no descendant anymore
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
if(id.equals(status.inReplyToId)) item.hasDescendantNeighbor=false;
}
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex-ancestorFirstIndex+1);
}
if(index==-1)
return;
if(index==-1) return;
int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(status.id))
break;
StatusDisplayItem item=displayItems.get(lastIndex);
String id=deleteContent ? item.getContentID() : item.parentID;
if(!id.equals(status.id)) break;
}
displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index);
}
protected void removeStatus(Status status){
boolean deleteContent=status==status.getContentStatus();
int ancestorFirstIndex=-1, ancestorLastIndex=-1;
for(int i=0;i<displayItems.size();i++){
StatusDisplayItem item=displayItems.get(i);
String id=deleteContent ? item.getContentID() : item.parentID;
if(id.equals(status.id)){
removeStatusDisplayItems(status, i, ancestorFirstIndex, ancestorLastIndex, deleteContent);
ancestorFirstIndex=ancestorLastIndex=-1;
continue;
}
if(id.equals(status.inReplyToId)){
if(ancestorFirstIndex==-1) ancestorFirstIndex=i;
ancestorLastIndex=i;
}
}
iterateRemoveStatus(data, status.id);
iterateRemoveStatus(preloadedData, status.id);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -316,6 +333,18 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
removeStatus(status);
}
@Subscribe
public void onReblogDeleted(ReblogDeletedEvent ev){
if(!ev.accountID.equals(accountID))
return;
for(Status item : data){
if(item.getContentStatus().id.equals(ev.statusID) && item.reblog!=null){
removeStatus(item);
break;
}
}
}
@Subscribe
public void onStatusCreated(StatusCreatedEvent ev){
if(!ev.accountID.equals(accountID))

View File

@@ -4,6 +4,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -65,6 +66,8 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
}
@Override
@@ -258,7 +261,7 @@ public class DiscoverAccountsFragment extends MastodonRecyclerFragment<DiscoverA
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);
UiUtils.setExtraTextInfo(getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
UiUtils.setExtraTextInfo(getContext(), null, true, false, false, item.account);
if(relationship==null){
actionWrap.setVisibility(View.GONE);

View File

@@ -2,8 +2,8 @@ package org.joinmastodon.android.fragments.discover;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@@ -15,14 +15,12 @@ import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.viewmodel.CardViewModel;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -33,11 +31,9 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.ListImageLoaderAdapter;
import me.grishka.appkit.imageloader.ListImageLoaderWrapper;
import me.grishka.appkit.imageloader.RecyclerViewDelegate;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
@@ -61,6 +57,8 @@ public class DiscoverNewsFragment extends BaseRecyclerFragment<CardViewModel> im
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
bannerHelper=new DiscoverInfoBannerHelper(DiscoverInfoBannerHelper.BannerType.TRENDING_LINKS, accountID);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
}
@Override

View File

@@ -1,5 +1,6 @@
package org.joinmastodon.android.fragments.discover;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
@@ -10,8 +11,6 @@ import org.joinmastodon.android.api.requests.trends.GetTrendingHashtags;
import org.joinmastodon.android.fragments.IsOnTop;
import org.joinmastodon.android.fragments.ScrollableToTop;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.utils.DiscoverInfoBannerHelper;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.HashtagChartView;
@@ -35,6 +34,8 @@ public class TrendingHashtagsFragment extends BaseRecyclerFragment<Hashtag> impl
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
accountID=getArguments().getString("account");
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N)
setRetainInstance(true);
}
@Override

View File

@@ -83,9 +83,10 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
@Override
protected void updateFilteredList(){
boolean addFakeInstance = currentSearchQuery.length()>0 && currentSearchQuery.matches("^\\S+\\.[^\\.]+$");
String query=getCurrentSearchQuery();
boolean addFakeInstance=query.length()>0 && query.matches("^\\S+\\.[^\\.]+$");
if(addFakeInstance){
fakeInstance.domain=fakeInstance.normalizedDomain=currentSearchQuery;
fakeInstance.domain=fakeInstance.normalizedDomain=query;
fakeInstance.description=getString(R.string.loading_instance);
if(filteredData.size()>0 && filteredData.get(0)==fakeInstance){
if(list.findViewHolderForAdapterPosition(1) instanceof InstanceViewHolder ivh){
@@ -99,12 +100,12 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment {
}
ArrayList<CatalogInstance> prevData=new ArrayList<>(filteredData);
filteredData.clear();
if(currentSearchQuery.length()>0){
if(query.length()>0){
boolean foundExactMatch=false;
for(CatalogInstance inst:data){
if(inst.normalizedDomain.contains(currentSearchQuery)){
if(inst.normalizedDomain.contains(query)){
filteredData.add(inst);
if(inst.normalizedDomain.equals(currentSearchQuery))
if(inst.normalizedDomain.equals(query))
foundExactMatch=true;
}
}

View File

@@ -93,10 +93,10 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
updateFilteredList();
searchEdit.removeCallbacks(searchDebouncer);
Instance instance=instancesCache.get(normalizeInstanceDomain(currentSearchQuery));
Instance instance=instancesCache.get(normalizeInstanceDomain(getCurrentSearchQuery()));
if(instance==null){
showProgressDialog();
loadInstanceInfo(currentSearchQuery, false);
loadInstanceInfo(getCurrentSearchQuery(), false);
}else{
proceedWithAuthOrSignup(instance);
}
@@ -106,7 +106,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
protected void onSearchChangedDebounced(){
currentSearchQuery=searchEdit.getText().toString().toLowerCase().trim();
updateFilteredList();
loadInstanceInfo(currentSearchQuery, false);
loadInstanceInfo(getCurrentSearchQuery(), false);
}
protected List<CatalogInstance> sortInstances(List<CatalogInstance> result){
@@ -126,9 +126,16 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
instanceProgressDialog.show();
}
protected String getCurrentSearchQuery(){
String[] parts=currentSearchQuery.split("@");
return parts.length>0 ? parts[parts.length-1] : "";
}
protected String normalizeInstanceDomain(String _domain){
if(TextUtils.isEmpty(_domain))
return null;
String[] parts=_domain.split("@");
_domain=parts[parts.length - 1];
if(_domain.contains(":")){
try{
_domain=Uri.parse(_domain).getAuthority();
@@ -198,7 +205,7 @@ abstract class InstanceCatalogFragment extends MastodonRecyclerFragment<CatalogI
instanceProgressDialog=null;
proceedWithAuthOrSignup(result);
}
if(Objects.equals(domain, currentSearchQuery) || Objects.equals(currentSearchQuery, redirects.get(domain)) || Objects.equals(currentSearchQuery, redirectsInverse.get(domain))){
if(Objects.equals(domain, getCurrentSearchQuery()) || Objects.equals(getCurrentSearchQuery(), redirects.get(domain)) || Objects.equals(getCurrentSearchQuery(), redirectsInverse.get(domain))){
boolean found=false;
for(CatalogInstance ci:filteredData){
if(ci.domain.equals(domain) && ci!=fakeInstance){

View File

@@ -227,7 +227,7 @@ public class ReportDoneFragment extends MastodonToolbarFragment{
@Override
protected int getNavigationIconDrawableResource(){
return R.drawable.ic_baseline_close_24;
return R.drawable.ic_fluent_dismiss_24_regular;
}
@Override

View File

@@ -6,8 +6,6 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField;
import org.joinmastodon.android.api.requests.instance.GetInstance;
import org.parceler.Parcel;
import java.time.Instant;
@@ -15,9 +13,6 @@ import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
/**
* Represents a user of Mastodon and their associated profile.
*/
@@ -163,26 +158,27 @@ public class Account extends BaseModel implements Searchable{
if(fields!=null){
for(AccountField f:fields)
f.postprocess();
} else {
fields = Collections.emptyList();
}else{
fields=Collections.emptyList();
}
if(emojis!=null){
for(Emoji e:emojis)
e.postprocess();
} else {
emojis = Collections.emptyList();
}else{
emojis=Collections.emptyList();
}
if(moved!=null)
moved.postprocess();
if(fqn == null) fqn = getFullyQualifiedName();
if(id == null) id = "";
if(username == null) username = "";
if(fqn==null) fqn=getFullyQualifiedName();
if(id==null) id="";
if(username==null) username="";
if(TextUtils.isEmpty(displayName))
displayName = !TextUtils.isEmpty(username) ? username : "";
if(acct == null) acct = "";
if(url == null) url = "";
if(note == null) note = "";
if(avatar == null) avatar = "";
displayName=!TextUtils.isEmpty(username) ? username : "";
if(acct==null) acct="";
if(url==null) url="";
if(note==null) note="";
if(avatar==null) avatar="";
if(displayName!=null) displayName='\u2068'+displayName+'\u2069';
}
public boolean isLocal(){

View File

@@ -52,7 +52,7 @@ public class Announcement extends BaseModel implements DisplayItemsParent {
public Status toStatus() {
Status s=Status.ofFake(id, content, publishedAt);
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.createdAt=startsAt != null ? startsAt : publishedAt;
s.reactions=reactions;
if(updatedAt != null) s.editedAt=updatedAt;
return s;

View File

@@ -71,7 +71,7 @@ public class SearchViewHelper{
searchLayout.addView(searchEdit, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
clearSearchButton=new ImageButton(context);
clearSearchButton.setImageResource(R.drawable.ic_baseline_close_24);
clearSearchButton.setImageResource(R.drawable.ic_fluent_dismiss_24_regular);
clearSearchButton.setContentDescription(context.getString(R.string.clear));
clearSearchButton.setImageTintList(ColorStateList.valueOf(UiUtils.getThemeColor(context, R.attr.colorM3OnSurfaceVariant)));
clearSearchButton.setBackground(UiUtils.getThemeDrawable(toolbarContext, android.R.attr.actionBarItemBackground));

View File

@@ -149,7 +149,7 @@ public class AccountCardStatusDisplayItem extends StatusDisplayItem{
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);
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), null, findViewById(R.id.pronouns), true, false, false, item.account);
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), null,true, false, false, item.account);
if(item.notification.type==Notification.Type.FOLLOW_REQUEST && (relationship==null || !relationship.followedBy)){
actionWrap.setVisibility(View.GONE);

View File

@@ -1,80 +1,52 @@
package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.Image;
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.Card;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.SingleImagePhotoViewerListener;
import org.joinmastodon.android.ui.drawables.BlurhashCrossfadeDrawable;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.List;
import me.grishka.appkit.Nav;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.utils.V;
public class FileStatusDisplayItem extends StatusDisplayItem{
private final Status status;
private final Attachment attachment;
public FileStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Attachment attachment, Status status){
public FileStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Attachment attachment) {
super(parentID, parentFragment);
this.status=status;
this.attachment=attachment;
}
@Override
public Type getType(){
public Type getType() {
return Type.FILE;
}
public static class Holder extends StatusDisplayItem.Holder<FileStatusDisplayItem>{
public static class Holder extends StatusDisplayItem.Holder<FileStatusDisplayItem> {
private final TextView title, domain;
private final ImageView icon;
private final Context context;
public Holder(Context context, ViewGroup parent){
public Holder(Context context, ViewGroup parent) {
super(context, R.layout.display_item_file, parent);
title=findViewById(R.id.title);
domain=findViewById(R.id.domain);
icon=findViewById(R.id.imageView);
this.context=context;
findViewById(R.id.inner).setOnClickListener(this::onClick);
}
@Override
public void onBind(FileStatusDisplayItem item) {
Uri url = Uri.parse(getUrl());
title.setText(item.attachment.description != null
? item.attachment.description
: url.getLastPathSegment());
title.setEllipsize(item.attachment.description != null ? TextUtils.TruncateAt.END : TextUtils.TruncateAt.MIDDLE);
domain.setText(url.getHost());
icon.setImageDrawable(context.getDrawable(R.drawable.ic_fluent_attach_24_regular));
title.setText(item.attachment.description != null
? item.attachment.description
: url.getLastPathSegment());
title.setEllipsize(item.attachment.description != null ? TextUtils.TruncateAt.END : TextUtils.TruncateAt.MIDDLE);
domain.setText(url.getHost());
}
private void onClick(View v) {
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), getUrl());
UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), getUrl());
}
private String getUrl() {

View File

@@ -18,7 +18,7 @@ import me.grishka.appkit.utils.V;
public class GapStatusDisplayItem extends StatusDisplayItem{
public boolean loading;
private Status status;
private final Status status;
public GapStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, Status status){
super(parentID, parentFragment);

View File

@@ -139,7 +139,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
public static class Holder extends StatusDisplayItem.Holder<HeaderStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView name, time, username, extraText, pronouns;
private final TextView name, time, username, extraText;
private final View collapseBtn, timeUsernameSeparator;
private final ImageView avatar, more, visibility, deleteNotification, unreadIndicator, markAsRead, collapseBtnIcon, botIcon;
private final PopupMenu optionsMenu;
@@ -166,7 +166,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
collapseBtn=findViewById(R.id.collapse_btn);
collapseBtnIcon=findViewById(R.id.collapse_btn_icon);
extraText=findViewById(R.id.extra_text);
pronouns=findViewById(R.id.pronouns);
avatar.setOnClickListener(this::onAvaClick);
avatar.setOutlineProvider(OutlineProviders.roundedRect(12));
avatar.setClipToOutline(true);
@@ -211,7 +210,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}else if(item.scheduledStatus!=null){
args.putString("sourceText", item.status.text);
args.putString("sourceSpoiler", item.status.spoilerText);
args.putBoolean("redraftStatus", true);
args.putParcelable("scheduledStatus", Parcels.wrap(item.scheduledStatus));
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}else{
@@ -221,14 +219,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
public void onSuccess(GetStatusSourceText.Response result){
args.putString("sourceText", result.text);
args.putString("sourceSpoiler", result.spoilerText);
if (result.contentType != null) {
if(result.contentType!=null){
args.putString("sourceContentType", result.contentType.name());
}
if (redraft) {
if(redraft){
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}, true);
} else {
}else{
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
}
}
@@ -245,7 +243,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
if (item.scheduledStatus != null) {
UiUtils.confirmDeleteScheduledPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.scheduledStatus, ()->{});
} else {
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{}, false);
}
}else if(id==R.id.pin || id==R.id.unpin) {
UiUtils.confirmPinPost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, !item.status.pinned, s->{});
@@ -337,7 +335,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
if(TextUtils.isEmpty(item.extraText)){
if (item.status != null) {
boolean displayPronouns=item.parentFragment instanceof ThreadFragment ? GlobalUserPreferences.displayPronounsInThreads : GlobalUserPreferences.displayPronounsInTimelines;
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), extraText, pronouns, displayPronouns, item.status.visibility==StatusPrivacy.DIRECT, item.status.localOnly || item.status.visibility==StatusPrivacy.LOCAL, item.status.account);
UiUtils.setExtraTextInfo(item.parentFragment.getContext(), extraText, displayPronouns, item.status.visibility==StatusPrivacy.DIRECT, item.status.localOnly || item.status.visibility==StatusPrivacy.LOCAL, item.status.account);
}
}else{
extraText.setVisibility(View.VISIBLE);

View File

@@ -10,10 +10,8 @@ import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
@@ -40,7 +38,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private StatusPrivacy visibility;
@DrawableRes
private int iconEnd;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(), fullTextEmojiHelper;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private View.OnClickListener handleClick;
public boolean needBottomPadding;
ReblogOrReplyLineStatusDisplayItem extra;
@@ -58,21 +56,13 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
HtmlParser.parseCustomEmoji(ssb, emojis);
this.text=ssb;
emojiHelper.setText(ssb);
this.fullText=fullText;
this.icon=icon;
this.status=status;
this.handleClick=handleClick;
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
updateVisibility(visibility);
if (fullText != null) {
fullTextEmojiHelper = new CustomEmojiHelper();
SpannableStringBuilder fullTextSsb = new SpannableStringBuilder(fullText);
HtmlParser.parseCustomEmoji(fullTextSsb, emojis);
this.fullText=fullTextSsb;
fullTextEmojiHelper.setText(fullTextSsb);
}
}
public void updateVisibility(StatusPrivacy visibility) {
@@ -92,34 +82,27 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
@Override
public int getImageCount(){
return emojiHelper.getImageCount();
return emojiHelper.getImageCount() + (extra!=null ? extra.emojiHelper.getImageCount() : 0);
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return emojiHelper.getImageRequest(index);
CustomEmojiHelper helper=index<emojiHelper.getImageCount() ? emojiHelper : extra.emojiHelper;
return helper.getImageRequest(index%emojiHelper.getImageCount());
}
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text, extraText;
private final View separator;
private final ViewGroup parent;
public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_reblog_or_reply_line, parent);
this.parent = parent;
text=findViewById(R.id.text);
extraText=findViewById(R.id.extra_text);
separator=findViewById(R.id.separator);
if (GlobalUserPreferences.compactReblogReplyLine) {
parent.addOnLayoutChangeListener((v, l, t, right, b, ol, ot, oldRight, ob) -> {
if (right != oldRight) layoutLine();
});
}
}
private void bindLine(ReblogOrReplyLineStatusDisplayItem item, TextView text) {
if (item.fullText != null) text.setContentDescription(item.fullText);
text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, item.iconEnd, 0);
text.setOnClickListener(item.handleClick);
@@ -133,7 +116,10 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
case LOCAL -> R.string.sk_local_only;
default -> 0;
} : 0;
if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")");
String visibilityDescription=visibilityText!=0 ? " (" + ctx.getString(visibilityText) + ")" : null;
text.setContentDescription(item.fullText==null && visibilityDescription==null ? null :
(item.fullText!=null ? item.fullText : item.text)
+ (visibilityDescription!=null ? visibilityDescription : ""));
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.N)
UiUtils.fixCompoundDrawableTintOnAndroid6(text);
text.setCompoundDrawableTintList(text.getTextColors());
@@ -146,31 +132,14 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
extraText.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
separator.setVisibility(item.extra == null ? View.GONE : View.VISIBLE);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), item.needBottomPadding ? V.dp(16) : 0);
layoutLine();
}
private void layoutLine() {
// layout line only if above header, compact and has extra
if (!GlobalUserPreferences.compactReblogReplyLine || item.extra == null) return;
itemView.measure(
View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED);
boolean isVertical = ((LinearLayout) itemView).getOrientation() == LinearLayout.VERTICAL;
extraText.setPaddingRelative(extraText.getPaddingStart(), item.extra != null && isVertical ? 0 : V.dp(16), extraText.getPaddingEnd(), extraText.getPaddingBottom());
separator.setVisibility(item.extra != null && !isVertical ? View.VISIBLE : View.GONE);
((LinearLayout) itemView).removeView(extraText);
if (isVertical) ((LinearLayout) itemView).addView(extraText);
else ((LinearLayout) itemView).addView(extraText, 0);
text.setText(isVertical ? item.fullText : item.text);
if (item.extra != null) {
extraText.setText(isVertical ? item.extra.fullText : item.extra.text);
}
}
@Override
public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image);
CustomEmojiHelper helper=index<item.emojiHelper.getImageCount() ? item.emojiHelper : item.extra.emojiHelper;
helper.setImageDrawable(index%item.emojiHelper.getImageCount(), image);
text.invalidate();
extraText.invalidate();
}
@Override

View File

@@ -13,6 +13,8 @@ import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountLocalPreferences;
@@ -22,6 +24,7 @@ import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTabFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.StatusListFragment;
import org.joinmastodon.android.fragments.ThreadFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment;
@@ -58,10 +61,10 @@ public abstract class StatusDisplayItem{
public boolean inset;
public int index;
public boolean
hasDescendantNeighbor = false,
hasAncestoringNeighbor = false,
isMainStatus = true,
isDirectDescendant = false;
hasDescendantNeighbor=false,
hasAncestoringNeighbor=false,
isMainStatus=true,
isDirectDescendant=false;
public static final int FLAG_INSET=1;
public static final int FLAG_NO_FOOTER=1 << 1;
@@ -89,6 +92,16 @@ public abstract class StatusDisplayItem{
this.parentFragment=parentFragment;
}
@NonNull
public String getContentID(){
if(parentFragment instanceof StatusListFragment slf){
Status s=slf.getContentStatusByID(parentID);
return s!=null ? s.id : parentID;
}else{
return parentID;
}
}
public abstract Type getType();
public int getImageCount(){
@@ -131,7 +144,7 @@ public abstract class StatusDisplayItem{
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
: 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)
@@ -169,7 +182,7 @@ public abstract class StatusDisplayItem{
statusForContent.rebloggedBy = status.account;
String fullText = fragment.getString(R.string.user_boosted, status.account.displayName);
String text = GlobalUserPreferences.compactReblogReplyLine && replyLine != null ? status.account.displayName : fullText;
String text = replyLine != null ? status.account.displayName : fullText;
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, text, status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20sp_filled, isOwnPost ? status.visibility : null, i->{
args.putParcelable("profileAccount", Parcels.wrap(status.account));
Nav.go(fragment.getActivity(), ProfileFragment.class, args);
@@ -197,7 +210,7 @@ public abstract class StatusDisplayItem{
.map(ReblogOrReplyLineStatusDisplayItem.class::cast)
.findFirst();
if (primaryLine.isPresent() && GlobalUserPreferences.compactReblogReplyLine) {
if (primaryLine.isPresent()) {
primaryLine.get().extra = replyLine;
} else {
items.add(replyLine);

View File

@@ -210,6 +210,7 @@ public class HtmlParser{
}
public static void parseCustomEmoji(SpannableStringBuilder ssb, List<Emoji> emojis){
if(emojis==null) return;
Map<String, Emoji> emojiByCode =
emojis.stream()
.collect(

View File

@@ -68,6 +68,7 @@ import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.CacheController;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.MastodonErrorResponse;
import org.joinmastodon.android.api.StatusInteractionController;
@@ -634,23 +635,25 @@ public class UiUtils {
.show();
}
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback) {
confirmDeletePost(activity, accountID, status, resultCallback, false);
}
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback, boolean forRedraft) {
Status s=status.getContentStatus();
showConfirmationAlert(activity,
forRedraft ? R.string.sk_confirm_delete_and_redraft_title : R.string.confirm_delete_title,
forRedraft ? R.string.sk_confirm_delete_and_redraft : R.string.confirm_delete,
forRedraft ? R.string.sk_delete_and_redraft : R.string.delete,
forRedraft ? R.drawable.ic_fluent_arrow_clockwise_28_regular : R.drawable.ic_fluent_delete_28_regular,
() -> new DeleteStatus(status.id)
() -> new DeleteStatus(s.id)
.setCallback(new Callback<>() {
@Override
public void onSuccess(Status result) {
resultCallback.accept(result);
AccountSessionManager.getInstance().getAccount(accountID).getCacheController().deleteStatus(status.id);
E.post(new StatusDeletedEvent(status.id, accountID));
CacheController cache=AccountSessionManager.get(accountID).getCacheController();
cache.deleteStatus(s.id);
E.post(new StatusDeletedEvent(s.id, accountID));
if(status!=s){
cache.deleteStatus(status.id);
E.post(new StatusDeletedEvent(status.id, accountID));
}
}
@Override
@@ -852,32 +855,35 @@ public class UiUtils {
() -> follow(activity, accountID, account, false, progressCallback, resultCallback),
() -> progressCallback.accept(false));
} else {
follow(activity, accountID, account, false, progressCallback, resultCallback);
Runnable action=()->{
progressCallback.accept(true);
new SetAccountFollowed(account.id, !relationship.following && !relationship.requested, true)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
progressCallback.accept(false);
if(!result.following && !result.requested){
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
}
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
progressCallback.accept(false);
}
})
.exec(accountID);
};
if(relationship.following && GlobalUserPreferences.confirmUnfollow){
showConfirmationAlert(activity, null, activity.getString(R.string.unfollow_confirmation, account.getDisplayUsername()), activity.getString(R.string.unfollow), R.drawable.ic_fluent_person_delete_24_regular, action);
}else{
action.run();
}
}
}
private static void follow(Activity activity, String accountID, Account account, boolean followed, Consumer<Boolean> progressCallback, Consumer<Relationship> resultCallback) {
progressCallback.accept(true);
new SetAccountFollowed(account.id, followed, true, false)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
progressCallback.accept(false);
if(!result.following && !result.requested){
E.post(new RemoveAccountPostsEvent(accountID, account.id, true));
}
}
@Override
public void onError(ErrorResponse error) {
error.showToast(activity);
progressCallback.accept(false);
}
})
.exec(accountID);
}
public static void handleFollowRequest(Activity activity, Account account, String accountID, @Nullable String notificationID, boolean accepted, Relationship relationship, Consumer<Relationship> resultCallback) {
if (accepted) {
@@ -1161,23 +1167,21 @@ public class UiUtils {
return back;
}
public static boolean setExtraTextInfo(Context ctx, @Nullable TextView extraText, @Nullable TextView pronouns, boolean displayPronouns, boolean mentionedOnly, boolean localOnly, @Nullable Account account) {
List<String> extraParts = extraText!=null && (localOnly || mentionedOnly) ? new ArrayList<>() : null;
Optional<String> p=pronouns==null || !displayPronouns ? Optional.empty() : extractPronouns(ctx, account);
if(p.isPresent()) {
HtmlParser.setTextWithCustomEmoji(pronouns, p.get(), account.emojis);
pronouns.setVisibility(View.VISIBLE);
}else if(pronouns!=null){
pronouns.setVisibility(View.GONE);
}
public static boolean setExtraTextInfo(Context ctx, @Nullable TextView extraText, boolean displayPronouns, boolean mentionedOnly, boolean localOnly, @Nullable Account account) {
List<String> extraParts=new ArrayList<>();
Optional<String> p=!displayPronouns ? Optional.empty() : extractPronouns(ctx, account);
if(localOnly)
extraParts.add(ctx.getString(R.string.sk_inline_local_only));
if(mentionedOnly)
extraParts.add(ctx.getString(R.string.sk_inline_direct));
if(extraText!=null && extraParts!=null && !extraParts.isEmpty()) {
String sepp = ctx.getString(R.string.sk_separator);
String text = String.join(" " + sepp + " ", extraParts);
if(account == null) extraText.setText(text);
if(p.isPresent() && extraParts.isEmpty())
extraParts.add(p.get());
if(extraText!=null && !extraParts.isEmpty()) {
String sepp=ctx.getString(R.string.sk_separator);
String text=String.join(" " + sepp + " ", extraParts);
if(account==null) extraText.setText(text);
else HtmlParser.setTextWithCustomEmoji(extraText, text, account.emojis);
extraText.setVisibility(View.VISIBLE);
return true;
@@ -1753,14 +1757,17 @@ public class UiUtils {
Matcher matcher=trimPronouns.matcher(text);
if(!matcher.find()) return null;
String matched=matcher.group(1);
String pronouns=matcher.group(1);
// crude fix to allow for pronouns like "it(/she)"
int missingClosingParens=0;
for(char c : matched.toCharArray()){
for(char c : pronouns.toCharArray()){
if(c=='(') missingClosingParens++;
if(c==')') missingClosingParens--;
}
return matched+")".repeat(Math.max(0, missingClosingParens));
pronouns+=")".repeat(Math.max(0, missingClosingParens));
// if ends with an un-closed custom emoji
if(pronouns.matches("^.*\\s+:[a-zA-Z_]+$")) pronouns+=':';
return pronouns;
}
// https://stackoverflow.com/questions/9475589/how-to-get-string-from-different-locales-in-android
@@ -1772,7 +1779,7 @@ public class UiUtils {
}
public static Optional<String> extractPronouns(Context context, @Nullable Account account) {
if (account == null) return Optional.empty();
if (account==null || account.fields==null) return Optional.empty();
String localizedPronouns=context.getString(R.string.sk_pronouns_label).toLowerCase();
// higher = worse. the lowest number wins. also i'm sorry for writing this

View File

@@ -4,11 +4,12 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
@@ -19,6 +20,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.HorizontalScrollView;
import android.widget.ImageButton;
import android.widget.ImageView;
@@ -27,8 +29,8 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
@@ -51,11 +53,8 @@ import org.parceler.Parcel;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
@@ -204,6 +203,15 @@ public class ComposeMediaViewController{
}
}
private void updateButton(ImageButton btn, @DrawableRes int drawableId, @StringRes int labelId){
btn.setImageResource(drawableId);
String label=fragment.getContext().getString(labelId);
btn.setContentDescription(label);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
btn.setTooltipText(label);
}
}
private View createMediaAttachmentView(DraftMediaAttachment draft){
View thumb=fragment.getActivity().getLayoutInflater().inflate(R.layout.compose_media_thumb, attachmentsView, false);
ImageView img=thumb.findViewById(R.id.thumb);
@@ -231,12 +239,11 @@ public class ComposeMediaViewController{
draft.removeButton.setOnClickListener(this::onRemoveMediaAttachmentClick);
draft.editButton.setTag(draft);
thumb.setOutlineProvider(OutlineProviders.roundedRect(12));
thumb.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
thumb.setClipToOutline(true);
img.setOutlineProvider(OutlineProviders.roundedRect(12));
img.setClipToOutline(true);
thumb.setBackgroundColor(UiUtils.getThemeColor(fragment.getActivity(), R.attr.colorM3Surface));
thumb.setOnLongClickListener(v->{
if(!v.hasTransientState() && attachments.size()>1){
attachmentsView.startDragging(v);
@@ -266,11 +273,11 @@ public class ComposeMediaViewController{
draft.subtitleView.setText(subtitleRes);
}
draft.titleView.setText(fragment.getString(R.string.attachment_x_percent_uploaded, 0));
draft.removeButton.setImageResource(R.drawable.ic_baseline_close_24);
updateButton(draft.removeButton, R.drawable.ic_fluent_dismiss_24_regular, R.string.delete);
if(draft.state==AttachmentUploadState.ERROR){
draft.titleView.setText(R.string.upload_failed);
draft.editButton.setImageResource(R.drawable.ic_fluent_arrow_counterclockwise_24_regular);
updateButton(draft.removeButton, R.drawable.ic_fluent_arrow_counterclockwise_24_regular, R.string.retry);
draft.editButton.setOnClickListener(this::onRetryOrCancelMediaUploadClick);
draft.progressBar.setVisibility(View.GONE);
draft.setUseErrorColors(true);
@@ -280,7 +287,7 @@ public class ComposeMediaViewController{
draft.editButton.setOnClickListener(this::onEditMediaDescriptionClick);
}else{
draft.editButton.setVisibility(View.GONE);
draft.removeButton.setImageResource(R.drawable.ic_baseline_close_24);
updateButton(draft.removeButton, R.drawable.ic_fluent_dismiss_24_regular, R.string.delete);
if(draft.state==AttachmentUploadState.PROCESSING){
draft.titleView.setText(R.string.upload_processing);
}else{
@@ -374,7 +381,7 @@ public class ComposeMediaViewController{
// attachment.retryButton.setContentDescription(fragment.getString(R.string.retry_upload));
V.setVisibilityAnimated(attachment.editButton, View.VISIBLE);
attachment.editButton.setImageResource(R.drawable.ic_fluent_arrow_counterclockwise_24_regular);
updateButton(attachment.editButton, R.drawable.ic_fluent_arrow_counterclockwise_24_regular, R.string.retry);
attachment.editButton.setOnClickListener(ComposeMediaViewController.this::onRetryOrCancelMediaUploadClick);
attachment.setUseErrorColors(true);
V.setVisibilityAnimated(attachment.progressBar, View.GONE);
@@ -478,8 +485,8 @@ public class ComposeMediaViewController{
throw new IllegalStateException("Unexpected state "+attachment.state);
attachment.uploadRequest=null;
attachment.state=AttachmentUploadState.DONE;
attachment.editButton.setImageResource(R.drawable.ic_fluent_edit_24_regular);
attachment.removeButton.setImageResource(R.drawable.ic_fluent_delete_24_regular);
updateButton(attachment.editButton, R.drawable.ic_fluent_edit_24_regular, R.string.sk_edit_alt_text);
updateButton(attachment.removeButton, R.drawable.ic_fluent_dismiss_24_regular, R.string.delete);
attachment.editButton.setOnClickListener(this::onEditMediaDescriptionClick);
V.setVisibilityAnimated(attachment.progressBar, View.GONE);
V.setVisibilityAnimated(attachment.editButton, View.VISIBLE);
@@ -708,18 +715,21 @@ public class ComposeMediaViewController{
if(errorTransitionAnimator!=null)
errorTransitionAnimator.cancel();
AnimatorSet set=new AnimatorSet();
int color1, color2, color3;
int defaultBg=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3Surface);
int errorBg=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3ErrorContainer);
int color2, color3;
if(use){
color1=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3ErrorContainer);
color2=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3Error);
color3=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3OnErrorContainer);
}else{
color1=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3Surface);
color2=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3OnSurface);
color3=UiUtils.getThemeColor(view.getContext(), R.attr.colorM3OnSurfaceVariant);
}
GradientDrawable bg=(GradientDrawable) view.getBackground().mutate();
ValueAnimator bgAnim=ValueAnimator.ofArgb(use ? defaultBg : errorBg, use ? errorBg : defaultBg);
bgAnim.addUpdateListener(anim->bg.setColor((Integer) anim.getAnimatedValue()));
set.playTogether(
ObjectAnimator.ofArgb(view, "backgroundColor", ((ColorDrawable)view.getBackground()).getColor(), color1),
bgAnim,
ObjectAnimator.ofArgb(titleView, "textColor", titleView.getCurrentTextColor(), color2),
ObjectAnimator.ofArgb(subtitleView, "textColor", subtitleView.getCurrentTextColor(), color3),
ObjectAnimator.ofArgb(removeButton.getDrawable(), "tint", subtitleView.getCurrentTextColor(), color3)

View File

@@ -6,6 +6,7 @@ import android.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.style.TypefaceSpan;
@@ -25,6 +26,7 @@ import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.ListsFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment;
import org.joinmastodon.android.model.Account;
@@ -98,6 +100,9 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
contextMenu=new PopupMenu(fragment.getActivity(), menuAnchor);
contextMenu.inflate(R.menu.profile);
contextMenu.setOnMenuItemClickListener(this::onContextMenuItemSelected);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.P && !UiUtils.isEMUI())
contextMenu.getMenu().setGroupDividerEnabled(true);
UiUtils.enablePopupMenuIcons(fragment.getContext(), contextMenu);
setStyle(AccessoryType.BUTTON, false);
}
@@ -212,14 +217,20 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
Menu menu=contextMenu.getMenu();
Account account=item.account;
menu.findItem(R.id.share).setTitle(fragment.getString(R.string.share_user, account.getDisplayUsername()));
menu.findItem(R.id.manage_user_lists).setTitle(fragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
menu.findItem(R.id.mute).setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getDisplayUsername()));
menu.findItem(R.id.block).setTitle(fragment.getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getDisplayUsername()));
menu.findItem(R.id.report).setTitle(fragment.getString(R.string.report_user, account.getDisplayUsername()));
MenuItem mute=menu.findItem(R.id.mute);
mute.setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
mute.setIcon(relationship.muting ? R.drawable.ic_fluent_speaker_0_24_regular : R.drawable.ic_fluent_speaker_off_24_regular);
UiUtils.insetPopupMenuIcon(fragment.getContext(), mute);
menu.findItem(R.id.block).setTitle(fragment.getString(relationship.blocking ? R.string.unblock_user : R.string.block_user, account.getShortUsername()));
menu.findItem(R.id.report).setTitle(fragment.getString(R.string.report_user, account.getShortUsername()));
menu.findItem(R.id.manage_user_lists).setVisible(relationship.following);
menu.findItem(R.id.soft_block).setVisible(relationship.followedBy && !relationship.following);
MenuItem hideBoosts=menu.findItem(R.id.hide_boosts);
if(relationship.following){
hideBoosts.setTitle(fragment.getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getDisplayUsername()));
hideBoosts.setTitle(fragment.getString(relationship.showingReblogs ? R.string.hide_boosts_from_user : R.string.show_boosts_from_user, account.getShortUsername()));
hideBoosts.setIcon(relationship.showingReblogs ? R.drawable.ic_fluent_arrow_repeat_all_off_24_regular : R.drawable.ic_fluent_arrow_repeat_all_24_regular);
UiUtils.insetPopupMenuIcon(fragment.getContext(), hideBoosts);
hideBoosts.setVisible(true);
}else{
hideBoosts.setVisible(false);
@@ -274,6 +285,8 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
UiUtils.confirmToggleMuteUser(fragment.getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}else if(id==R.id.block){
UiUtils.confirmToggleBlockUser(fragment.getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}else if(id==R.id.soft_block){
UiUtils.confirmSoftBlockUser(fragment.getActivity(), accountID, account, this::updateRelationship);
}else if(id==R.id.report){
Bundle args=new Bundle();
args.putString("account", accountID);
@@ -303,6 +316,12 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
})
.wrapProgress(fragment.getActivity(), R.string.loading, false)
.exec(accountID);
}else if(id==R.id.manage_user_lists){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("profileAccount", account.id);
args.putString("profileDisplayUsername", account.getDisplayUsername());
Nav.go(fragment.getActivity(), ListsFragment.class, args);
}
return true;
}

View File

@@ -36,10 +36,12 @@ public class HeaderSubtitleLinearLayout extends LinearLayout{
LayoutParams lp=(LayoutParams) v.getLayoutParams();
remainingWidth-=v.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
}
View first=getChildAt(0);
if(first instanceof TextView){
// guaranteeing at least 64dp of width for the display name
((TextView) first).setMaxWidth(Math.max(remainingWidth, V.dp(64)));
if(getChildAt(0) instanceof TextView first){
// guaranteeing at least 64sp of width for the display name
first.setMaxWidth(Math.max(remainingWidth, V.sp(64)));
}
if(getChildAt(1) instanceof TextView second){
second.setMaxWidth(Math.max(remainingWidth, V.sp(120)));
}
}else{
View first=getChildAt(0);

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?colorM3Surface" />
<stroke android:color="?colorM3OutlineVariant" android:width="1dp"/>
<corners android:radius="11dp"/>
<corners android:radius="12dp"/>
</shape>

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@@ -5,8 +5,7 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingHorizontal="16dp"
android:paddingBottom="4dp">
android:paddingHorizontal="16dp">
<Button
android:id="@+id/language_btn"
@@ -18,14 +17,17 @@
android:drawableStart="@drawable/ic_fluent_local_language_16_regular"
android:drawablePadding="8dp"
android:drawableTint="?colorM3OnSurfaceVariant"
android:contentDescription="@string/language"
android:tooltipText="@string/language"
android:textColor="?colorM3OnSurfaceVariant" />
<ImageButton
android:id="@+id/drafts_btn"
style="@style/Widget.Mastodon.M3.Button.Text"
android:background="@drawable/bg_button_m3_text_circle"
android:layout_width="40dp"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_marginStart="-6dp"
android:src="@drawable/ic_fluent_clock_20_regular"
android:tint="?colorM3OnSurfaceVariant"
android:contentDescription="@string/sk_schedule_or_draft"
@@ -48,7 +50,7 @@
android:id="@+id/publish_btn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginStart="6dp"
android:singleLine="true"
android:ellipsize="end"
android:visibility="gone"

View File

@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="@drawable/fg_compose_attachment">
android:background="@drawable/bg_compose_attachment">
<View
android:id="@+id/drag_layer"
@@ -18,6 +18,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/title"
android:layout_margin="1dp"
android:scaleType="centerCrop"
android:importantForAccessibility="no"
tools:src="#0f0"/>
@@ -57,9 +58,11 @@
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_fluent_delete_24_regular"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/delete"
android:tooltipText="@string/delete"
android:src="@drawable/ic_fluent_dismiss_24_regular"
android:tint="?colorM3OnSurfaceVariant"
android:backgroundTint="?colorM3OnSurfaceVariant"
android:background="@drawable/bg_round_ripple"/>
@@ -70,8 +73,9 @@
android:layout_height="48dp"
android:layout_toStartOf="@id/delete"
android:layout_alignParentBottom="true"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:layout_marginBottom="4dp"
android:contentDescription="@string/sk_edit_alt_text"
android:tooltipText="@string/sk_edit_alt_text"
android:src="@drawable/ic_fluent_edit_24_regular"
android:tint="?colorM3OnSurfaceVariant"
android:backgroundTint="?colorM3OnSurfaceVariant"

View File

@@ -21,8 +21,7 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="4dp">
<!-- avatar width (46sp) / 2 - button width (24dp) / 2 -->
android:paddingHorizontal="15dp">
<FrameLayout
android:layout_weight="1"

View File

@@ -125,20 +125,6 @@
android:gravity="start|center_vertical"
tools:text="Eugen" />
<TextView
android:id="@+id/pronouns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8sp"
android:maxWidth="161sp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/m3_title_medium"
android:fontFamily="sans-serif"
android:textAlignment="viewStart"
android:textColor="?colorM3OnSurface"
tools:text="they/them" />
<TextView
android:id="@+id/extra_text"
android:layout_width="wrap_content"

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<org.joinmastodon.android.ui.views.AutoOrientationLinearLayout
<org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
@@ -47,4 +47,4 @@
android:singleLine="true"
android:ellipsize="end"/>
</org.joinmastodon.android.ui.views.AutoOrientationLinearLayout>
</org.joinmastodon.android.ui.views.HeaderSubtitleLinearLayout>

View File

@@ -128,9 +128,9 @@
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="6dp"
android:layout_marginStart="16dp"
android:layout_gravity="bottom"
android:paddingTop="6dp"
android:paddingTop="3dp"
android:gravity="center_vertical"
android:minHeight="44dp">
@@ -249,7 +249,7 @@
android:paddingStart="8dp"
android:paddingEnd="4dp"
android:clipToPadding="false"
android:paddingBottom="8dp"
android:paddingBottom="10dp"
android:visibility="gone">
<org.joinmastodon.android.ui.views.ProgressBarButton
@@ -286,7 +286,7 @@
android:paddingStart="4dp"
android:paddingEnd="16dp"
android:clipToPadding="false"
android:paddingBottom="8dp"
android:paddingBottom="10dp"
android:visibility="gone">
<org.joinmastodon.android.ui.views.ProgressBarButton
@@ -322,7 +322,7 @@
android:layout_gravity="bottom"
android:paddingStart="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:paddingBottom="10dp"
android:clipToPadding="false">
<org.joinmastodon.android.ui.views.ProgressBarButton

View File

@@ -35,7 +35,7 @@
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end|top"
android:src="@drawable/ic_baseline_close_24"
android:src="@drawable/ic_fluent_dismiss_24_regular"
android:tint="?colorM3DarkOnSurface"
android:background="?android:actionBarItemBackground"/>

View File

@@ -1,15 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/open_with_account" android:title="@string/sk_open_with_account" android:icon="@drawable/ic_fluent_person_swap_24_regular">
<menu android:id="@+id/accounts" />
</item>
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
<item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_mute_24_regular"/>
<item android:id="@+id/block" android:title="@string/block_user" android:icon="@drawable/ic_fluent_person_prohibited_24_regular"/>
<item android:id="@+id/report" android:title="@string/report_user" android:icon="@drawable/ic_fluent_warning_24_regular"/>
<item android:id="@+id/soft_block" android:title="@string/sk_remove_follower" android:icon="@drawable/ic_fluent_person_delete_24_regular"/>
<item android:id="@+id/block_domain" android:title="@string/block_domain" android:icon="@drawable/ic_fluent_shield_prohibited_24_regular"/>
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user" android:icon="@drawable/ic_fluent_arrow_repeat_all_off_24_regular"/>
<item android:id="@+id/manage_user_lists" android:title="@string/sk_lists_with_user" android:icon="@drawable/ic_fluent_people_24_regular"/>
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser" android:icon="@drawable/ic_fluent_globe_24_regular"/>
<group android:id="@+id/menu_group1">
<item android:id="@+id/open_with_account" android:title="@string/sk_open_with_account" android:visible="false" android:icon="@drawable/ic_fluent_person_swap_24_regular">
<menu android:id="@+id/accounts" />
</item>
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser" android:icon="@drawable/ic_fluent_globe_24_regular"/>
</group>
<group android:id="@+id/menu_group2">
<item android:id="@+id/manage_user_lists" android:title="@string/sk_lists_with_user" android:icon="@drawable/ic_fluent_people_24_regular"/>
<item android:id="@+id/mute" android:title="@string/mute_user" android:icon="@drawable/ic_fluent_speaker_off_24_regular"/>
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user" android:icon="@drawable/ic_fluent_arrow_repeat_all_off_24_regular"/>
<item android:id="@+id/block" android:title="@string/block_user" android:icon="@drawable/ic_fluent_person_prohibited_24_regular"/>
<item android:id="@+id/soft_block" android:title="@string/sk_remove_follower" android:icon="@drawable/ic_fluent_person_delete_24_regular"/>
<item android:id="@+id/report" android:title="@string/report_user" android:icon="@drawable/ic_fluent_warning_24_regular"/>
<item android:id="@+id/block_domain" android:title="@string/block_domain" android:icon="@drawable/ic_fluent_shield_prohibited_24_regular"/>
</group>
</menu>

View File

@@ -38,7 +38,7 @@
<string name="sk_settings_enable_marquee">Laufschrift in Titelleisten deaktivieren</string>
<string name="sk_settings_contribute">Zu Megalodon beitragen</string>
<string name="sk_settings_show_federated_timeline">Föderierte Timeline anzeigen</string>
<string name="sk_notification_type_posts">Beitrags-Benachrichtigungen</string>
<string name="sk_notification_type_posts">Beitrags-Benachrichtigungen</string>
<string name="sk_settings_color_palette">Farbschema</string>
<string name="sk_color_palette_pink">Pink</string>
<string name="sk_color_palette_purple">Violett</string>

View File

@@ -599,7 +599,21 @@
<item quantity="one">%,d ανάρτηση σήμερα</item>
<item quantity="other">%,d αναρτήσεις σήμερα</item>
</plurals>
<string name="error_playing_video">Σφάλμα αναπαραγωγής βίντεο</string>
<string name="timeline_following">Ακολουθείς</string>
<string name="lists">Λίστες</string>
<string name="followed_hashtags">Ετικέτες που ακολουθούνται</string>
<string name="no_lists">Δεν έχεις καμία λίστα ακόμα.</string>
<string name="no_followed_hashtags">Δεν ακολουθείς καμία ετικέτα.</string>
<string name="manage_lists">Διαχείριση λιστών</string>
<string name="manage_hashtags">Διαχείριση ετικετών</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Αναπτυσσόμενο μενού</string>
<string name="edit_list">Επεξεργασία λίστας</string>
<string name="list_members">Μέλη λίστας</string>
<string name="delete_list">Διαγραφή λίστας</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">Διαγραφή “%s”;</string>
<string name="list_exclusive">Απόκρυψη μελών στο Ακολουθείς</string>
<!-- %s is a username -->
</resources>

View File

@@ -405,4 +405,7 @@
<string name="sk_muted_accounts">Cuentas silenciadas</string>
<string name="sk_settings_like_icon">Utilizar el corazón como icono favorito</string>
<string name="sk_recently_used">Utilizado recientemente</string>
<string name="sk_set_as_default">Establecer por defecto</string>
<string name="sk_settings_color_palette_default">Por defecto (%s)</string>
<string name="sk_settings_underlined_links">Enlaces subrayados</string>
</resources>

View File

@@ -402,7 +402,10 @@
<string name="sk_time_days">%d jours</string>
<string name="sk_trending_posts_info_banner">Ces publications gagnent actuellement du terrain sur le Fediverse.</string>
<string name="sk_blocked_accounts">Comptes bloqués</string>
<string name="sk_muted_accounts">Comptes silenciés</string>
<string name="sk_muted_accounts">Comptes masqués</string>
<string name="sk_settings_like_icon">Utiliser le cœur comme icône pour les favoris</string>
<string name="sk_recently_used">Utilisé récemment</string>
<string name="sk_set_as_default">Définir par défaut</string>
<string name="sk_settings_color_palette_default">Par défaut (%s)</string>
<string name="sk_settings_underlined_links">Liens soulignés</string>
</resources>

View File

@@ -396,6 +396,9 @@
<string name="sk_post_contains_media">Kiriman berisi media</string>
<string name="sk_blocked_accounts">Akun yang diblokir</string>
<string name="sk_muted_accounts">Akun yang dibisukan</string>
<string name="sk_settings_like_icon">Gunakam hati sebagai ikon favorit</string>
<string name="sk_settings_like_icon">Gunakan hati sebagai ikon favorit</string>
<string name="sk_recently_used">Baru-baru ini digunakan</string>
<string name="sk_set_as_default">Tetapkan sebagai bawaan</string>
<string name="sk_settings_color_palette_default">Bawaan (%s)</string>
<string name="sk_settings_underlined_links">Tautan yang digarisbawahi</string>
</resources>

View File

@@ -401,4 +401,7 @@
<string name="sk_muted_accounts">Conturi amuțite</string>
<string name="sk_settings_like_icon">Folosiți inimă ca iconița favorite</string>
<string name="sk_recently_used">Recent folosit</string>
<string name="sk_set_as_default">Setați ca implicit</string>
<string name="sk_settings_color_palette_default">Implicit (%s)</string>
<string name="sk_settings_underlined_links">Link-uri subliniate</string>
</resources>

View File

@@ -4,17 +4,18 @@
<string name="next">Nästa</string>
<string name="loading_instance">Hämtar serverinfo…</string>
<string name="error">Fel</string>
<string name="not_a_mastodon_instance">%s verkar inte vara en mastodon server.</string>
<string name="not_a_mastodon_instance">%s verkar inte vara en mastodonserver.</string>
<string name="ok">OK</string>
<string name="preparing_auth">Förbereder för autentisering…</string>
<string name="finishing_auth">Slutför autentisering…</string>
<string name="user_boosted">%s boostade</string>
<string name="in_reply_to">Som svar på %s</string>
<string name="notifications">Notiser</string>
<string name="notifications">Aviseringar</string>
<string name="user_followed_you">%s följde dig</string>
<string name="user_sent_follow_request">%s skickade en förfrågan om att följa dig</string>
<string name="user_favorited">%s favoritmarkerade ditt inlägg</string>
<string name="notification_boosted">%s boostade ditt inlägg</string>
<string name="poll_ended">Se resultatet av en omröstning du röstade i</string>
<string name="share_toot_title">Dela</string>
<string name="settings">Inställningar</string>
<string name="publish">Publicera</string>
@@ -105,7 +106,7 @@
<string name="do_unblock">Avblockera</string>
<string name="button_blocked">Blockerad</string>
<string name="action_vote">Rösta</string>
<string name="delete">Radera</string>
<string name="delete">Ta bort</string>
<string name="confirm_delete_title">Radera inlägg</string>
<string name="confirm_delete">Är du säker på att du vill radera detta inlägg?</string>
<string name="deleting">Raderar…</string>
@@ -118,12 +119,15 @@
<string name="hashtags">Hashtaggar</string>
<string name="news">Nyheter</string>
<string name="for_you">För dig</string>
<string name="all_notifications">Alla</string>
<string name="mentions">Omnämningar</string>
<plurals name="x_people_talking">
<item quantity="one">%d person pratar</item>
<item quantity="other">%d personer pratar</item>
</plurals>
<string name="report_title">Rapportera %s</string>
<string name="report_choose_reason">Vad är fel med det här inlägget?</string>
<string name="report_choose_reason_account">Vad är fel med %s?</string>
<string name="report_choose_reason_subtitle">Välj den bästa träffen</string>
<string name="report_reason_personal">Jag gillar det inte</string>
<string name="report_reason_personal_subtitle">Det är inget som du vill se</string>
@@ -131,6 +135,7 @@
<string name="report_reason_spam_subtitle">Skadliga länkar, bedrägligt beteende eller repetitiva svar</string>
<string name="report_reason_violation">Det bryter mot serverns regler</string>
<string name="report_reason_violation_subtitle">Du är medveten om att det bryter mot specifika regler</string>
<string name="report_reason_other">Det är något annat</string>
<string name="report_reason_other_subtitle">Frågan passar inte in i andra kategorier</string>
<string name="report_choose_rule">Vilka regler överträds?</string>
<string name="report_choose_rule_subtitle">Välj alla som stämmer</string>
@@ -139,9 +144,14 @@
<string name="report_comment_title">Finns det något annat vi borde veta?</string>
<string name="report_comment_hint">Ytterligare kommentarer</string>
<string name="sending_report">Skickar rapport…</string>
<string name="report_sent_title">Tack för att du rapporterar, vi kommer att titta på detta.</string>
<string name="report_sent_subtitle">Medan vi granskar detta kan du vidta åtgärder mot %s:</string>
<string name="unfollow_user">Avfölj %s</string>
<string name="unfollow">Avfölj</string>
<string name="mute_user_explain">Du kommer inte se deras inlägg. De kan fortfarande följa dig och se dina inlägg. De kommer inte veta att de är tystade.</string>
<string name="block_user_explain">Du kommer inte se deras inlägg. De kommer inte kunna se dina inlägg eller följa dig. De kommer kunna se att de är blockerade.</string>
<string name="report_personal_title">Vill du inte se detta?</string>
<string name="report_personal_subtitle">Här är dina alternativ för att bestämma vad du ser på Mastodon:</string>
<string name="back">Tillbaka</string>
<string name="search_communities">Servernamn eller URL</string>
<string name="instance_rules_title">Serverregler</string>
@@ -168,19 +178,23 @@
<string name="category_tech">Teknik</string>
<string name="confirm_email_title">Kolla din inkorg</string>
<!-- %s is the email address -->
<string name="confirm_email_subtitle">Klicka på länken som vi har skickat till dig för att bekräfta %s. Vi väntar här.</string>
<string name="confirm_email_didnt_get">Fick du ingen länk?</string>
<string name="resend">Skicka igen</string>
<string name="open_email_app">Öppna e-postappen</string>
<string name="resent_email">Bekräftelse via e-post skickad</string>
<string name="compose_hint">Skriv eller klistra in vad du har på hjärtat</string>
<string name="content_warning">Innehållsvarning</string>
<string name="save">Spara</string>
<string name="add_alt_text">Lägg till alternativtext</string>
<string name="visibility_public">Offentlig</string>
<string name="visibility_followers_only">Endast följare</string>
<string name="visibility_private">Bara omnämnda personer</string>
<string name="recent_searches">Nyligen</string>
<string name="skip">Hoppa över</string>
<string name="notification_type_follow">Nya följare</string>
<string name="notification_type_favorite">Favoriter</string>
<string name="notification_type_reblog">Boostar</string>
<string name="notification_type_mention">Omnämningar</string>
<string name="notification_type_poll">Omröstningar</string>
<string name="choose_account">Välj konto</string>
@@ -188,6 +202,7 @@
<string name="media_attachment_unsupported_type">Filen %s är av en typ som inte stöds</string>
<string name="media_attachment_too_big">Filen %1$s överskrider storleksgränsen på %2$s MB</string>
<string name="settings_theme">Utseende</string>
<string name="theme_auto">Använd systeminställning</string>
<string name="theme_light">Ljust</string>
<string name="theme_dark">Mörkt</string>
<string name="settings_behavior">Beteende</string>
@@ -201,6 +216,8 @@
<string name="settings_app_version">Mastodon för Android v%1$s (%2$d)</string>
<string name="media_cache_cleared">Mediacache rensad</string>
<string name="confirm_log_out">Logga ut från %s?</string>
<string name="sensitive_content_explain">Författaren markerade detta medium som känsligt.</string>
<string name="avatar_description">Gå till %ss profil</string>
<string name="more_options">Fler alternativ</string>
<string name="new_post">Nytt inlägg</string>
<string name="button_reply">Svara</string>
@@ -217,7 +234,10 @@
<string name="follow_user">Följ %s</string>
<string name="unfollowed_user">Avföljde %s</string>
<string name="followed_user">Du följer nu %s</string>
<string name="following_user_requested">Begär att följa %s</string>
<string name="open_in_browser">Öppna i webbläsare</string>
<string name="hide_boosts_from_user">Dölj boostar från %s</string>
<string name="show_boosts_from_user">Visa boostar från %s</string>
<string name="signup_reason">Varför vill du gå med?</string>
<string name="signup_reason_note">Detta kommer hjälpa oss att granska din ansökan.</string>
<string name="clear">Rensa</string>
@@ -231,7 +251,12 @@
<string name="error_saving_file">Fel vid sparande av fil</string>
<string name="file_saved">Filen sparad</string>
<string name="downloading">Laddar ner…</string>
<string name="no_app_to_handle_action">Det finns ingen app för att hantera denna åtgärd</string>
<string name="local_timeline">Lokal</string>
<string name="trending_posts_info_banner">Detta är de inlägg som engagerar på Mastodon.</string>
<string name="trending_links_info_banner">Det här är nyheterna som det talas om på Mastodon.</string>
<!-- %s is the server domain -->
<string name="local_timeline_info_banner">Detta är alla inlägg från alla användare på din server (%s).</string>
<string name="recommended_accounts_info_banner">Du kanske gillar dessa konton baserat på andra konton du följer.</string>
<string name="see_new_posts">Se nya inlägg</string>
<string name="load_missing_posts">Ladda saknade inlägg</string>
@@ -306,6 +331,7 @@
<string name="login_title">Välkommen tillbaka</string>
<string name="login_subtitle">Logga in på den server där du skapade ditt konto.</string>
<string name="server_url">Server-URL</string>
<string name="signup_random_server_explain">Vi kommer att välja en server baserat på ditt språk om du fortsätter utan att göra ett val.</string>
<string name="server_filter_any_language">Vilket språk som helst</string>
<string name="server_filter_instant_signup">Omedelbar registrering</string>
<string name="server_filter_manual_review">Manuell granskning</string>
@@ -327,13 +353,16 @@
<string name="popular_on_mastodon">Populärt på Mastodon</string>
<string name="follow_all">Följ alla</string>
<string name="server_rules_disagree">Instämmer inte alls</string>
<string name="privacy_policy_explanation">TL;DR: Vi samlar inte in eller bearbetar något.</string>
<!-- %s is server domain -->
<string name="server_policy_disagree">Håller inte med %s</string>
<string name="profile_bio">Biografi</string>
<!-- Shown in a progress dialog when you tap "follow all" -->
<string name="sending_follows">Följer användare…</string>
<!-- %1$s is server domain, %2$s is email domain. You can reorder these placeholders to fit your language better. -->
<string name="signup_email_domain_blocked">%1$s tillåter inte registrering från %2$s. Prova en annan eller &lt;a&gt;välj en annan server&lt;/a&gt;.</string>
<string name="spoiler_show">Visa ändå</string>
<string name="spoiler_hide">Dölj igen</string>
<string name="poll_multiple_choice">Välj en eller flera</string>
<string name="save_changes">Spara ändringar</string>
<string name="profile_timeline">Tidslinje</string>
@@ -349,9 +378,12 @@
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
<string name="welcome_paragraph1">Mastodon är ett decentraliserat socialt nätverk, vilket innebär att inget enskilt företag kontrollerar det. Det består av många oberoende servrar, alla sammankopplade.</string>
<string name="what_are_servers">Vad är servrar?</string>
<string name="opening_link">Öppnar länk…</string>
<string name="link_not_supported">Denna länk stöds inte i appen</string>
<string name="log_out_all_accounts">Logga ut från alla konton</string>
<string name="confirm_log_out_all_accounts">Logga ut från alla konton?</string>
<string name="retry">Försök igen</string>
<string name="post_failed">Misslyckades att skapa inlägg</string>
<!-- %s is formatted file size ("467 KB image") -->
<string name="attachment_description_image">%s bild</string>
<string name="attachment_description_video">%s video</string>
@@ -362,33 +394,80 @@
<string name="attachment_type_audio">Ljud</string>
<string name="attachment_type_gif">GIF</string>
<string name="attachment_type_unknown">Fil</string>
<string name="add_poll_option">Lägg till röstningsalternativ</string>
<string name="poll_length">Undersökningens längd</string>
<string name="poll_style">Stil</string>
<string name="compose_poll_single_choice">Välj en</string>
<string name="compose_poll_multiple_choice">Flera val</string>
<string name="delete_poll_option">Ta bort röstningsalternativ</string>
<string name="poll_style_title">Undersökningsstil</string>
<string name="alt_text">Alt-Text</string>
<string name="help">Hjälp</string>
<string name="what_is_alt_text">Vad är alt-text?</string>
<string name="edit_post">Redigera inlägg</string>
<string name="no_verified_link">Ingen verifierad länk</string>
<string name="compose_autocomplete_emoji_empty">Bläddra bland emoji</string>
<string name="compose_autocomplete_users_empty">Hitta den du söker efter</string>
<string name="no_search_results">Kunde inte hitta något för dessa söktermer</string>
<string name="language">Språk</string>
<string name="language_default">Standard</string>
<string name="language_system">System</string>
<string name="language_detecting">Identifiera språk</string>
<string name="language_cant_detect">Det gick inte att identifiera språk</string>
<string name="language_detected">Identifierat</string>
<string name="media_hidden">Dold media</string>
<string name="post_hidden">Inlägg dolt</string>
<string name="report_title_post">Rapportera inlägg</string>
<string name="forward_report_explanation">Kontot kommer från en annan server. Vill du skicka en anonymiserad kopia av rapporten dit också?</string>
<!-- %s is the server domain -->
<string name="forward_report_to_server">Vidarebefordra till %s</string>
<!-- Shown on the "stamp" on the screen that appears after you report a post/user. Please keep the translation short, preferably a single word -->
<string name="reported">Rapporterad</string>
<string name="muted_user">Tystade %s</string>
<string name="report_sent_already_blocked">Du har redan blockerat den här användaren, så det finns inget annat du behöver göra medan vi granskar din rapport.</string>
<string name="report_personal_already_blocked">Du har redan blockerat den här användaren, så det finns inget annat du behöver göra.\n\nTack för att du hjälper till att hålla Mastodon en säker plats för alla!</string>
<string name="blocked_user">Blockerade %s</string>
<string name="mark_all_notifications_read">Markera alla som lästa</string>
<string name="settings_display">Skärm</string>
<string name="settings_filters">Filter</string>
<string name="settings_server_explanation">Översikt, regler och moderatorer</string>
<!-- %s is the app name (Mastodon, key app_name). I made it a placeholder so everything Just Works™ with forks -->
<string name="about_app">Om %s</string>
<string name="default_post_language">Standardspråk för inlägg</string>
<string name="settings_alt_text_reminders">Lägg till alt-textpåminnelser</string>
<string name="settings_confirm_unfollow">Fråga innan du avföljer någon</string>
<string name="settings_confirm_boost">Fråga innan du boostar</string>
<string name="settings_confirm_delete_post">Fråga innan du raderar inlägg</string>
<string name="pause_all_notifications">Pausa alla</string>
<string name="pause_notifications_off">Av</string>
<string name="notifications_policy_anyone">Alla</string>
<string name="notifications_policy_followed">Personer som följer dig</string>
<string name="notifications_policy_follower">Personer du följer</string>
<string name="notifications_policy_no_one">Ingen</string>
<string name="settings_notifications_policy">Få aviseringar från</string>
<string name="notification_type_mentions_and_replies">Omnämningar och svar</string>
<string name="pause_all_notifications_title">Pausa alla aviseringar</string>
<plurals name="x_weeks">
<item quantity="one">%d vecka</item>
<item quantity="other">%d veckor</item>
</plurals>
<!-- %1$s is the date (may be relative, e.g. "today" or "yesterday"), %2$s is the time. You can reorder these placeholders if that works better for your language -->
<string name="today">idag</string>
<string name="yesterday">igår</string>
<string name="tomorrow">i morgon</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="pause_notifications_banner">Aviseringar kommer att återupptas %s.</string>
<string name="resume_notifications_now">Återuppta nu</string>
<string name="open_system_notification_settings">Gå till aviseringsinställningar</string>
<string name="about_server">Om</string>
<string name="server_rules">Regler</string>
<string name="server_administrator">Administratör</string>
<string name="send_email_to_server_admin">Meddela administratör</string>
<string name="settings_even_more">Ännu fler inställningar</string>
<string name="settings_show_cws">Visa innehållsvarningar</string>
<string name="settings_hide_sensitive_media">Dölj media markerad som känsligt</string>
<string name="settings_show_interaction_counts">Antal inläggsinteraktioner</string>
<string name="settings_show_emoji_in_names">Anpassad emoji i visningsnamn</string>
<plurals name="in_x_hours">
<item quantity="one">om %d timme</item>
@@ -398,29 +477,51 @@
<item quantity="one">%d timme sedan</item>
<item quantity="other">%d timmar sedan</item>
</plurals>
<string name="count_one">En</string>
<string name="count_two">Två</string>
<string name="count_three">Tre</string>
<string name="count_four">Fyra</string>
<!-- %s is the username -->
<string name="unfollow_confirmation">Avfölj %s?</string>
<string name="filter_active">Aktiv</string>
<string name="filter_inactive">Inaktiv</string>
<string name="settings_add_filter">Lägg till filter</string>
<string name="settings_edit_filter">Redigera filter</string>
<string name="settings_filter_duration">Varaktighet</string>
<string name="settings_filter_muted_words">Tystade ord</string>
<string name="settings_filter_context">Tysta från</string>
<string name="settings_filter_show_cw">Visa med innehållsvarning</string>
<string name="settings_delete_filter">Radera filter</string>
<string name="filter_duration_forever">För alltid</string>
<!-- %s is the timestamp ("tomorrow at 12:34") -->
<string name="selection_2_options">%1$s och %2$s</string>
<string name="selection_3_options">%1$s, %2$s och %3$s</string>
<string name="filter_context_home_lists">Hem &amp; listor</string>
<string name="filter_context_notifications">Aviseringar</string>
<string name="filter_context_public_timelines">Publika tidslinjer</string>
<string name="filter_context_threads_replies">Trådar &amp; svar</string>
<string name="filter_context_profiles">Profiler</string>
<string name="settings_filter_title">Rubrik</string>
<string name="settings_delete_filter_title">Ta bort filter ”%s”?</string>
<string name="settings_delete_filter_confirmation">Detta filter kommer raderas från ditt konto på alla dina enheter.</string>
<string name="add_muted_word">Lägg till tystat ord</string>
<string name="edit_muted_word">Redigera tystat ord</string>
<string name="add">Lägg till</string>
<string name="filter_word_or_phrase">Ord eller fras</string>
<string name="settings_delete_filter_word">Ta bort ordet ”%s”?</string>
<string name="enter_selection_mode">Välj</string>
<string name="select_all">Välj alla</string>
<string name="settings_filter_duration_title">Filtervaraktighet</string>
<string name="filter_duration_custom">Anpassad</string>
<plurals name="settings_delete_x_filter_words">
<item quantity="one">Radera %d ord?</item>
<item quantity="other">Radera %d ord?</item>
</plurals>
<string name="required_form_field_blank">Får inte vara tomt</string>
<string name="filter_word_already_in_list">Redan finns i listan</string>
<string name="app_update_ready">Appuppdateringen är redo</string>
<string name="app_update_version">Version %s</string>
<string name="downloading_update">Ladda ner (%d%%)</string>
<!-- Shown like a content warning, %s is the name of the filter -->
<string name="post_matches_filter_x">Matchar filter \"%s\"</string>
<string name="search_mastodon">Sök i Mastodon</string>
@@ -428,6 +529,8 @@
<string name="search_open_url">Öppna URL i Mastodon</string>
<string name="posts_matching_hashtag">Inlägg med \"%s\"</string>
<string name="search_go_to_account">Gå till %s</string>
<string name="posts_matching_string">Inlägg med \"%s\"</string>
<string name="accounts_matching_string">Personer med ”%s”</string>
<!-- Shown in the post header. Please keep it short -->
<string name="time_seconds_ago_short">%ds sedan</string>
<string name="time_minutes_ago_short">%dm sedan</string>
@@ -436,7 +539,38 @@
<!-- %s is the name of the post language -->
<string name="translate_post">Översätt från %s</string>
<!-- %1$s is the language, %2$s is the name of the translation service -->
<string name="post_translated">Översatt från %1$s med %2$s</string>
<string name="translation_show_original">Visa original</string>
<string name="translation_failed">Översättningen misslyckades. Det kan hända att administratören inte har aktiverat översättningar på den här servern eller att servern kör en äldre version av Mastodon som inte har stöd för översättningar ännu.</string>
<string name="settings_privacy">Integritet och räckvidd</string>
<string name="settings_indexable">Inkludera offentliga inlägg i sökresultaten</string>
<string name="error_playing_video">Fel vid videouppspelning</string>
<string name="timeline_following">Följer</string>
<string name="lists">Listor</string>
<string name="followed_hashtags">Följda hashtaggar</string>
<string name="no_lists">Du har inga listor ännu.</string>
<string name="no_followed_hashtags">Du följer inga hashtaggar.</string>
<string name="manage_lists">Hantera listor</string>
<string name="manage_hashtags">Hantera hashtaggar</string>
<!-- Screen reader description for the menu on the home timeline screen -->
<string name="dropdown_menu">Nedrullningsmeny</string>
<string name="edit_list">Redigera lista</string>
<string name="list_members">Lista medlemmar</string>
<string name="delete_list">Ta bort lista</string>
<!-- %s is the name of the list -->
<string name="delete_list_confirm">Ta bort “%s”?</string>
<string name="list_exclusive">Dölj medlemmar i Följer</string>
<string name="list_name">Listnamn</string>
<string name="list_show_replies_to">Visa svar till</string>
<string name="list_replies_no_one">Ingen</string>
<string name="list_replies_members">Listmedlemmar</string>
<string name="confirm_remove_list_members">Ta bort medlemmar?</string>
<string name="remove">Ta bort</string>
<string name="add_list_member">Lägg till medlem</string>
<string name="search_among_people_you_follow">Sök bland personer du följer</string>
<string name="add_user_to_list">Lägg till i lista…</string>
<string name="add_user_to_list_title">Lägg till i lista</string>
<!-- %s is a username -->
<string name="remove_from_list">Ta bort från lista</string>
<string name="confirm_remove_list_member">Ta bort medlem?</string>
</resources>

View File

@@ -406,4 +406,7 @@
<string name="sk_muted_accounts">Ігноровані облікові записи</string>
<string name="sk_settings_like_icon">Використовувати серце як піктограму «Вподобане»</string>
<string name="sk_recently_used">Недавно використані</string>
<string name="sk_settings_underlined_links">Підкреслені посилання</string>
<string name="sk_set_as_default">Установити типово</string>
<string name="sk_settings_color_palette_default">Усталена (%s)</string>
</resources>

View File

@@ -397,4 +397,7 @@
<string name="sk_muted_accounts">已静音账号</string>
<string name="sk_settings_like_icon">使用心形作为收藏图标</string>
<string name="sk_recently_used">最近使用</string>
<string name="sk_set_as_default">设为默认</string>
<string name="sk_settings_color_palette_default">默认(%s</string>
<string name="sk_settings_underlined_links">链接加下划线</string>
</resources>

View File

@@ -293,7 +293,7 @@
<string name="sk_expand">Expand</string>
<string name="sk_collapse">Collapse</string>
<string name="sk_settings_collapse_long_posts">Collapse very long posts</string>
<string name="sk_unfinished_attachments">Fix attachments?</string>
<string name="sk_unfinished_attachments">Uploading attachments</string>
<string name="sk_unfinished_attachments_message">Some attachments havent finished uploading.</string>
<string name="sk_settings_hide_interaction">Hide interaction buttons</string>
<string name="sk_follow_as">Follow from other account</string>
@@ -411,4 +411,5 @@
<string name="sk_recently_used">Recently used</string>
<string name="sk_settings_underlined_links">Underlined links</string>
<string name="sk_set_as_default">Set as default</string>
<string name="sk_edit_alt_text">Edit alt text</string>
</resources>