Merge remote-tracking branch 'megalodon_main/main'

# Conflicts:
#	mastodon/build.gradle
#	mastodon/src/main/java/org/joinmastodon/android/api/requests/accounts/SetPrivateNote.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java
#	mastodon/src/main/java/org/joinmastodon/android/model/TimelineDefinition.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java
#	mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
#	mastodon/src/main/res/drawable/bg_note_edit.xml
#	mastodon/src/main/res/layout/fragment_profile.xml
#	metadata/uk/full_description.txt
This commit is contained in:
LucasGGamerM 2023-11-17 16:02:23 -03:00
commit da5186c7b5
78 changed files with 1289 additions and 713 deletions

View file

@ -81,7 +81,6 @@ android {
versionNameSuffix '-play' versionNameSuffix '-play'
} }
githubRelease { initWith release } githubRelease { initWith release }
playRelease { initWith release }
fdroidRelease { initWith release } fdroidRelease { initWith release }
} }
compileOptions { compileOptions {

View file

@ -257,5 +257,9 @@ public class UiUtilsTest {
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount( assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "-- * (asterisk) --") makeField("pronouns", "-- * (asterisk) --")
)).orElseThrow()); )).orElseThrow());
assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "they/(she?)...")
)).orElseThrow());
} }
} }

View file

@ -39,18 +39,44 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels; import org.parceler.Parcels;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import me.grishka.appkit.FragmentStackActivity; import me.grishka.appkit.FragmentStackActivity;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback; import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent { public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
private static final String TAG="MainActivity";
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState){ protected void onCreate(@Nullable Bundle savedInstanceState){
AccountSession session=getCurrentSession(); AccountSession session=getCurrentSession();
UiUtils.setUserPreferredTheme(this, session); UiUtils.setUserPreferredTheme(this, session);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Thread.UncaughtExceptionHandler defaultHandler=Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e)->{
File file=new File(MastodonApp.context.getFilesDir(), "crash.log");
try(FileOutputStream out=new FileOutputStream(file)){
PrintWriter writer=new PrintWriter(out);
writer.println(BuildConfig.VERSION_NAME+" ("+BuildConfig.VERSION_CODE+")");
writer.println(Instant.now().toString());
writer.println();
e.printStackTrace(writer);
writer.flush();
}catch(IOException x){
Log.e(TAG, "Error writing crash.log", x);
}finally{
defaultHandler.uncaughtException(t, e);
}
});
if(savedInstanceState==null){ if(savedInstanceState==null){
restartHomeFragment(); restartHomeFragment();
} }

View file

@ -53,7 +53,9 @@ public class MastodonAPIController{
.registerTypeAdapter(Status.class, new Status.StatusDeserializer()) .registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.create(); .create();
private static WorkerThread thread=new WorkerThread("MastodonAPIController"); private static WorkerThread thread=new WorkerThread("MastodonAPIController");
private static OkHttpClient httpClient=new OkHttpClient.Builder().build(); private static OkHttpClient httpClient=new OkHttpClient.Builder()
.readTimeout(30, TimeUnit.SECONDS)
.build();
private AccountSession session; private AccountSession session;
private static List<String> badDomains = new ArrayList<>(); private static List<String> badDomains = new ArrayList<>();

View file

@ -0,0 +1,11 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.AkkomaTranslation;
public class AkkomaTranslateStatus extends MastodonAPIRequest<AkkomaTranslation>{
public AkkomaTranslateStatus(String id, String lang){
super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
}
}

View file

@ -48,6 +48,8 @@ public class CreateStatus extends MastodonAPIRequest<Status>{
public String quoteId; public String quoteId;
public ContentType contentType; public ContentType contentType;
public boolean preview;
public static class Poll{ public static class Poll{
public ArrayList<String> options=new ArrayList<>(); public ArrayList<String> options=new ArrayList<>();
public int expiresIn; public int expiresIn;

View file

@ -260,11 +260,13 @@ public class AccountSession{
} }
private boolean isFilteredType(Status s){ private boolean isFilteredType(Status s){
AccountLocalPreferences localPreferences = getLocalPreferences();
return (!localPreferences.showReplies && s.inReplyToId != null) return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null); || (!localPreferences.showBoosts && s.reblog != null);
} }
public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){ public <T> void filterStatusContainingObjects(List<T> objects, Function<T, Status> extractor, FilterContext context, Account profile){
AccountLocalPreferences localPreferences = getLocalPreferences();
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){ if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
Status s=extractor.apply(obj); Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){ if(s!=null && s.filtered!=null){
@ -307,7 +309,7 @@ public class AccountSession{
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC)) if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true; return true;
// Even with server-side filters, clients are expected to remove statuses that match a filter that hides them // Even with server-side filters, clients are expected to remove statuses that match a filter that hides them
if(localPreferences.serverSideFiltersSupported){ if(getLocalPreferences().serverSideFiltersSupported){
for(FilterResult filter : s.filtered){ for(FilterResult filter : s.filtered){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE) if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true; return true;

View file

@ -1,5 +1,7 @@
package org.joinmastodon.android.api.session; package org.joinmastodon.android.api.session;
import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
import android.app.Activity; import android.app.Activity;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.ComponentName; import android.content.ComponentName;
@ -34,6 +36,7 @@ import org.joinmastodon.android.model.EmojiCategory;
import org.joinmastodon.android.model.LegacyFilter; import org.joinmastodon.android.model.LegacyFilter;
import org.joinmastodon.android.model.Instance; import org.joinmastodon.android.model.Instance;
import org.joinmastodon.android.model.Token; import org.joinmastodon.android.model.Token;
import org.unifiedpush.android.connector.UnifiedPush;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -112,6 +115,7 @@ public class AccountSessionManager{
} }
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){ public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
Context context = MastodonApp.context;
instances.put(instance.uri, instance); instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo); AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
sessions.put(session.getID(), session); sessions.put(session.getID(), session);
@ -124,7 +128,14 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri)); MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, instance.uri));
updateMoreInstanceInfo(instance, instance.uri); updateMoreInstanceInfo(instance, instance.uri);
if(PushSubscriptionManager.arePushNotificationsAvailable()){ if (!UnifiedPush.getDistributor(context).isEmpty()) {
UnifiedPush.registerApp(
context,
session.getID(),
new ArrayList<>(),
context.getPackageName()
);
} else if(PushSubscriptionManager.arePushNotificationsAvailable()){
session.getPushSubscriptionManager().registerAccountForPush(null); session.getPushSubscriptionManager().registerAccountForPush(null);
} }
maybeUpdateShortcuts(); maybeUpdateShortcuts();

View file

@ -23,13 +23,16 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.E; import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships; import org.joinmastodon.android.api.requests.accounts.GetAccountRelationships;
import org.joinmastodon.android.api.requests.polls.SubmitPollVote; import org.joinmastodon.android.api.requests.polls.SubmitPollVote;
import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus;
import org.joinmastodon.android.api.requests.statuses.TranslateStatus; import org.joinmastodon.android.api.requests.statuses.TranslateStatus;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.AkkomaTranslation;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll; import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.Relationship;
@ -53,6 +56,7 @@ import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem;
import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.MediaAttachmentViewController; import org.joinmastodon.android.ui.utils.MediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController; import org.joinmastodon.android.ui.utils.PreviewlessMediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
@ -66,6 +70,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -441,12 +446,14 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
}); });
list.addItemDecoration(new StatusListItemDecoration()); list.addItemDecoration(new StatusListItemDecoration());
list.addItemDecoration(new InsetStatusItemDecoration(this));
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){ ((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
private Rect tmpRect=new Rect(); private Rect tmpRect=new Rect();
@Override @Override
public void getSelectorBounds(View view, Rect outRect){ public void getSelectorBounds(View view, Rect outRect){
boolean hasDescendant = false, hasAncestor = false, isWarning = false; if(list!=view.getParent()) return;
int lastIndex = -1, firstIndex = -1; boolean hasDescendant=false, hasAncestor=false, isWarning=false;
int lastIndex=-1, firstIndex=-1;
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){ if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
list.getDecoratedBoundsWithMargins(view, outRect); list.getDecoratedBoundsWithMargins(view, outRect);
}else{ }else{
@ -576,7 +583,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1)); spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
} }
pollItems.clear(); pollItems.clear();
StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems, status); StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
if(spoilerItem!=null){ if(spoilerItem!=null){
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear(); spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems); spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
@ -647,7 +654,8 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){ public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
Status status=holder.getItem().status; Status status=holder.getItem().status;
toggleSpoiler(status, holder.getItemID()); boolean isForQuote=holder.getItem().isForQuote;
toggleSpoiler(status, isForQuote, holder.getItemID());
} }
public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) { public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder) {
@ -669,15 +677,16 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class); else notifyItemChangedBefore(holder.getItem(), HeaderStatusDisplayItem.class);
} }
protected void toggleSpoiler(Status status, String itemID){ protected void toggleSpoiler(Status status, boolean isForQuote, String itemID){
status.spoilerRevealed=!status.spoilerRevealed; status.spoilerRevealed=!status.spoilerRevealed;
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
status.sensitiveRevealed = false; status.sensitiveRevealed = false;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class); List<SpoilerStatusDisplayItem.Holder> spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0);
if(spoiler!=null) spoiler.rebind(); if(spoiler!=null) spoiler.rebind();
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class); else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class)); SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
int index=displayItems.indexOf(spoilerItem); int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){ if(status.spoilerRevealed){
@ -941,21 +950,43 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
status.translationState=Status.TranslationState.SHOWN; status.translationState=Status.TranslationState.SHOWN;
}else{ }else{
status.translationState=Status.TranslationState.LOADING; status.translationState=Status.TranslationState.LOADING;
new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()) Consumer<Translation> successCallback=(result)->{
.setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()==null)
return;
status.translation=result; status.translation=result;
status.translationState=Status.TranslationState.SHOWN; status.translationState=Status.TranslationState.SHOWN;
updateTranslation(itemID); updateTranslation(itemID);
};
MastodonAPIRequest<?> req=isInstanceAkkoma()
? new AkkomaTranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(AkkomaTranslation result){
if(getActivity()!=null) successCallback.accept(result.toTranslation());
}
@Override
public void onError(ErrorResponse error){
if(getActivity()!=null) translationCallbackError(status, itemID);
}
})
: new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage()).setCallback(new Callback<>(){
@Override
public void onSuccess(Translation result){
if(getActivity()!=null) successCallback.accept(result);
} }
@Override @Override
public void onError(ErrorResponse error){ public void onError(ErrorResponse error){
if(getActivity()==null) if(getActivity()!=null) translationCallbackError(status, itemID);
return; }
});
// 1 minute
req.setTimeout(60000).exec(accountID);
}
}
}
updateTranslation(itemID);
}
private void translationCallbackError(Status status, String itemID) {
status.translationState=Status.TranslationState.HIDDEN; status.translationState=Status.TranslationState.HIDDEN;
updateTranslation(itemID); updateTranslation(itemID);
new M3AlertDialogBuilder(getActivity()) new M3AlertDialogBuilder(getActivity())
@ -964,13 +995,6 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show(); .show();
} }
})
.exec(accountID);
}
}
}
updateTranslation(itemID);
}
private void updateTranslation(String itemID) { private void updateTranslation(String itemID) {
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class); TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
@ -981,6 +1005,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
notifyItemChanged(itemID, TextStatusDisplayItem.class); notifyItemChanged(itemID, TextStatusDisplayItem.class);
} }
if(isInstanceAkkoma())
return;
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class); SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null){ if(spoiler!=null){
spoiler.rebind(); spoiler.rebind();

View file

@ -801,7 +801,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id))) || (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
&& !status.spoilerText.startsWith("re: ") ? "re: " : ""; && !status.spoilerText.startsWith("re: ") ? "re: " : "";
spoilerEdit.setText(prefix + replyTo.spoilerText); spoilerEdit.setText(prefix + status.spoilerText);
spoilerBtn.setSelected(true); spoilerBtn.setSelected(true);
} }
if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language); if (status.language != null && !status.language.isEmpty()) setPostLanguage(status.language);
@ -890,19 +890,22 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
charCounter.setText(String.valueOf(charLimit)); charCounter.setText(String.valueOf(charLimit));
} }
// draftsBtn = wrap.findViewById(R.id.drafts_btn); // draftsBtn=wrap.findViewById(R.id.drafts_btn);
draftOptionsPopup = new PopupMenu(getContext(), draftsBtn); draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
draftOptionsPopup.inflate(R.menu.compose_more); draftOptionsPopup.inflate(R.menu.compose_more);
draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft); Menu draftOptionsMenu=draftOptionsPopup.getMenu();
undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft); draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule); undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule); scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
draftOptionsPopup.setOnMenuItemClickListener(i->{ draftOptionsPopup.setOnMenuItemClickListener(i->{
int id = i.getItemId(); int id=i.getItemId();
if (id == R.id.draft) updateScheduledAt(getDraftInstant()); if(id==R.id.draft) updateScheduledAt(getDraftInstant());
else if (id == R.id.schedule) pickScheduledDateTime(); else if(id==R.id.schedule) pickScheduledDateTime();
else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null); else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
else navigateToUnsentPosts(); else if(id==R.id.drafts) navigateToUnsentPosts();
else if(id==R.id.preview) publish(true);
return true; return true;
}); });
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup); UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
@ -917,6 +920,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
return false; return false;
}); });
if (!GlobalUserPreferences.relocatePublishButton):
publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setOnClickListener(v->{ (GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setOnClickListener(v->{
Consumer<Boolean> draftCheckComplete=(isDraft)->{ Consumer<Boolean> draftCheckComplete=(isDraft)->{
@ -1140,6 +1145,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
private void publish(){ private void publish(){
publish(false);
}
private void publish(boolean preview){
sendingOverlay=new View(getActivity()); sendingOverlay=new View(getActivity());
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams(); WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@ -1153,10 +1162,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false); (GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false);
V.setVisibilityAnimated(sendProgress, View.VISIBLE); V.setVisibilityAnimated(sendProgress, View.VISIBLE);
mediaViewController.saveAltTextsBeforePublishing(this::actuallyPublish, this::handlePublishError); mediaViewController.saveAltTextsBeforePublishing(
()->actuallyPublish(preview),
this::handlePublishError);
} }
private void actuallyPublish(){ private void actuallyPublish(boolean preview){
String text=mainEditText.getText().toString(); String text=mainEditText.getText().toString();
CreateStatus.Request req=new CreateStatus.Request(); CreateStatus.Request req=new CreateStatus.Request();
if("bottom".equals(postLang.encoding)){ if("bottom".equals(postLang.encoding)){
@ -1174,6 +1185,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.sensitive=sensitive; req.sensitive=sensitive;
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType; req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
req.scheduledAt=scheduledAt; req.scheduledAt=scheduledAt;
req.preview=preview;
if(!mediaViewController.isEmpty()){ if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs(); req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){ if(editingStatus != null){
@ -1201,7 +1213,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Callback<Status> resCallback=new Callback<>(){ Callback<Status> resCallback=new Callback<>(){
@Override @Override
public void onSuccess(Status result){ public void onSuccess(Status result){
maybeDeleteScheduledPost(() -> { if(preview){
openPreview(result);
return;
}
maybeDeleteScheduledPost(()->{
wm.removeView(sendingOverlay); wm.removeView(sendingOverlay);
sendingOverlay=null; sendingOverlay=null;
if(editingStatus==null || redraftStatus){ if(editingStatus==null || redraftStatus){
@ -1223,10 +1240,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
E.post(new StatusUpdatedEvent(editedStatus)); E.post(new StatusUpdatedEvent(editedStatus));
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) { if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()){
Nav.finish(ComposeFragment.this); Nav.finish(ComposeFragment.this);
} }
if (getArguments().getBoolean("navigateToStatus", false)) { if(getArguments().getBoolean("navigateToStatus", false)){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result)); args.putParcelable("status", Parcels.wrap(result));
@ -1242,11 +1259,11 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
}; };
if(editingStatus!=null && !redraftStatus){ if(editingStatus!=null && !redraftStatus && !preview){
new EditStatus(req, editingStatus.id) new EditStatus(req, editingStatus.id)
.setCallback(resCallback) .setCallback(resCallback)
.exec(accountID); .exec(accountID);
}else if(req.scheduledAt == null){ }else if(req.scheduledAt == null || preview){
new CreateStatus(req, uuid) new CreateStatus(req, uuid)
.setCallback(resCallback) .setCallback(resCallback)
.exec(accountID); .exec(accountID);
@ -1299,6 +1316,25 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
} }
private void openPreview(Status result){
result.preview=true;
wm.removeView(sendingOverlay);
sendingOverlay=null;
publishButton.setEnabled(true);
V.setVisibilityAnimated(sendProgress, View.GONE);
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(contentView.getWindowToken(), 0);
Bundle args=new Bundle();
args.putString("account", accountID);
args.putParcelable("status", Parcels.wrap(result));
if(replyTo!=null){
args.putParcelable("inReplyTo", Parcels.wrap(replyTo));
args.putParcelable("inReplyToAccount", Parcels.wrap(replyTo.account));
}
Nav.go(getActivity(), ThreadFragment.class, args);
}
private void updateRecentLanguages() { private void updateRecentLanguages() {
if (postLang == null || postLang.language == null) return; if (postLang == null || postLang.language == null) return;
String language = postLang.language.getLanguage(); String language = postLang.language.getLanguage();
@ -1665,6 +1701,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
} }
contentTypePopup.setOnMenuItemClickListener(i->{ contentTypePopup.setOnMenuItemClickListener(i->{
uuid=null;
int index=i.getItemId(); int index=i.getItemId();
contentType=ContentType.values()[index]; contentType=ContentType.values()[index];
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal()); btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());

View file

@ -67,53 +67,53 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop { public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefinition> implements ScrollableToTop{
private String accountID; private String accountID;
private TimelinesAdapter adapter; private TimelinesAdapter adapter;
private final ItemTouchHelper itemTouchHelper; private final ItemTouchHelper itemTouchHelper;
private Menu optionsMenu; private Menu optionsMenu;
private boolean updated; private boolean updated;
private final Map<MenuItem, TimelineDefinition> timelineByMenuItem = new HashMap<>(); private final Map<MenuItem, TimelineDefinition> timelineByMenuItem=new HashMap<>();
private final List<ListTimeline> listTimelines = new ArrayList<>(); private final List<ListTimeline> listTimelines=new ArrayList<>();
private final List<Hashtag> hashtags = new ArrayList<>(); private final List<Hashtag> hashtags=new ArrayList<>();
private MenuItem addHashtagItem; private MenuItem addHashtagItem;
private final List<CustomLocalTimeline> localTimelines = new ArrayList<>(); private final List<CustomLocalTimeline> localTimelines = new ArrayList<>();
public EditTimelinesFragment() { public EditTimelinesFragment(){
super(10); super(10);
ItemTouchHelper.SimpleCallback itemTouchCallback = new ItemTouchHelperCallback() ; ItemTouchHelper.SimpleCallback itemTouchCallback=new ItemTouchHelperCallback();
itemTouchHelper = new ItemTouchHelper(itemTouchCallback); itemTouchHelper=new ItemTouchHelper(itemTouchCallback);
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
setTitle(R.string.sk_timelines); setTitle(R.string.sk_timelines);
accountID = getArguments().getString("account"); accountID=getArguments().getString("account");
new GetLists().setCallback(new Callback<>() { new GetLists().setCallback(new Callback<>(){
@Override @Override
public void onSuccess(List<ListTimeline> result) { public void onSuccess(List<ListTimeline> result){
listTimelines.addAll(result); listTimelines.addAll(result);
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error){
error.showToast(getContext()); error.showToast(getContext());
} }
}).exec(accountID); }).exec(accountID);
new GetFollowedHashtags().setCallback(new Callback<>() { new GetFollowedHashtags().setCallback(new Callback<>(){
@Override @Override
public void onSuccess(HeaderPaginationList<Hashtag> result) { public void onSuccess(HeaderPaginationList<Hashtag> result){
hashtags.addAll(result); hashtags.addAll(result);
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
public void onError(ErrorResponse error) { public void onError(ErrorResponse error){
error.showToast(getContext()); error.showToast(getContext());
} }
}).exec(accountID); }).exec(accountID);
@ -126,7 +126,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
itemTouchHelper.attachToRecyclerView(list); itemTouchHelper.attachToRecyclerView(list);
refreshLayout.setEnabled(false); refreshLayout.setEnabled(false);
@ -134,14 +134,14 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){
this.optionsMenu = menu; this.optionsMenu=menu;
updateOptionsMenu(); updateOptionsMenu();
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item){
if (item.getItemId() == R.id.menu_back) { if(item.getItemId()==R.id.menu_back){
updateOptionsMenu(); updateOptionsMenu();
optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0); optionsMenu.performIdentifierAction(R.id.menu_add_timeline, 0);
return true; return true;
@ -161,7 +161,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
return true; return true;
} }
private void addTimeline(TimelineDefinition tl) { private void addTimeline(TimelineDefinition tl){
data.add(tl.copy()); data.add(tl.copy());
adapter.notifyItemInserted(data.size()); adapter.notifyItemInserted(data.size());
saveTimelines(); saveTimelines();
@ -194,26 +194,26 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
timelineByMenuItem.put(item, tl); timelineByMenuItem.put(item, tl);
} }
private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon) { private MenuItem addOptionsItem(Menu menu, String name, @DrawableRes int icon){
MenuItem item = menu.add(0, View.generateViewId(), Menu.NONE, name); MenuItem item=menu.add(0, View.generateViewId(), Menu.NONE, name);
item.setIcon(icon); item.setIcon(icon);
return item; return item;
} }
private void updateOptionsMenu() { private void updateOptionsMenu(){
if(getActivity()==null) return; if(getActivity()==null) return;
optionsMenu.clear(); optionsMenu.clear();
timelineByMenuItem.clear(); timelineByMenuItem.clear();
SubMenu menu = optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add); SubMenu menu=optionsMenu.addSubMenu(0, R.id.menu_add_timeline, NONE, R.string.sk_timelines_add);
menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); menu.getItem().setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular); menu.getItem().setIcon(R.drawable.ic_fluent_add_24_regular);
SubMenu timelinesMenu = menu.addSubMenu(R.string.sk_timeline); SubMenu timelinesMenu=menu.addSubMenu(R.string.sk_timeline);
timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular); timelinesMenu.getItem().setIcon(R.drawable.ic_fluent_timeline_24_regular);
SubMenu listsMenu = menu.addSubMenu(R.string.sk_list); SubMenu listsMenu=menu.addSubMenu(R.string.sk_list);
listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular); listsMenu.getItem().setIcon(R.drawable.ic_fluent_people_24_regular);
SubMenu hashtagsMenu = menu.addSubMenu(R.string.sk_hashtag); SubMenu hashtagsMenu=menu.addSubMenu(R.string.sk_hashtag);
hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular); hashtagsMenu.getItem().setIcon(R.drawable.ic_fluent_number_symbol_24_regular);
MenuItem addLocalTimelines = menu.add(0, R.id.menu_add_local_timelines, NONE, R.string.local_timeline); MenuItem addLocalTimelines = menu.add(0, R.id.menu_add_local_timelines, NONE, R.string.local_timeline);
@ -223,19 +223,19 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
makeBackItem(listsMenu); makeBackItem(listsMenu);
makeBackItem(hashtagsMenu); makeBackItem(hashtagsMenu);
TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl -> addTimelineToOptions(tl, timelinesMenu)); TimelineDefinition.getAllTimelines(accountID).stream().forEach(tl->addTimelineToOptions(tl, timelinesMenu));
listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl -> addTimelineToOptions(tl, listsMenu)); listTimelines.stream().map(TimelineDefinition::ofList).forEach(tl->addTimelineToOptions(tl, listsMenu));
addHashtagItem = addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular); addHashtagItem=addOptionsItem(hashtagsMenu, getContext().getString(R.string.sk_timelines_add), R.drawable.ic_fluent_add_24_regular);
hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl -> addTimelineToOptions(tl, hashtagsMenu)); hashtags.stream().map(TimelineDefinition::ofHashtag).forEach(tl->addTimelineToOptions(tl, hashtagsMenu));
timelinesMenu.getItem().setVisible(timelinesMenu.size() > 0); timelinesMenu.getItem().setVisible(timelinesMenu.size()>0);
listsMenu.getItem().setVisible(listsMenu.size() > 0); listsMenu.getItem().setVisible(listsMenu.size()>0);
hashtagsMenu.getItem().setVisible(hashtagsMenu.size() > 0); hashtagsMenu.getItem().setVisible(hashtagsMenu.size()>0);
UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline); UiUtils.enableOptionsMenuIcons(getContext(), optionsMenu, R.id.menu_add_timeline);
} }
private void saveTimelines() { private void saveTimelines(){
updated=true; updated=true;
AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences(); AccountLocalPreferences prefs=AccountSessionManager.get(accountID).getLocalPreferences();
if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE); if(data.isEmpty()) data.add(TimelineDefinition.HOME_TIMELINE);
@ -243,7 +243,7 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
prefs.save(); prefs.save();
} }
private void removeTimeline(int position) { private void removeTimeline(int position){
data.remove(position); data.remove(position);
adapter.notifyItemRemoved(position); adapter.notifyItemRemoved(position);
saveTimelines(); saveTimelines();
@ -257,29 +257,29 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
@Override @Override
protected RecyclerView.Adapter<TimelineViewHolder> getAdapter() { protected RecyclerView.Adapter<TimelineViewHolder> getAdapter(){
return adapter = new TimelinesAdapter(); return adapter=new TimelinesAdapter();
} }
@Override @Override
public void scrollToTop() { public void scrollToTop(){
smoothScrollRecyclerViewToTop(list); smoothScrollRecyclerViewToTop(list);
} }
@Override @Override
public void onDestroy() { public void onDestroy(){
super.onDestroy(); super.onDestroy();
if (updated) UiUtils.restartApp(); if(updated) UiUtils.restartApp();
} }
private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags) { private boolean setTagListContent(NachoTextView editText, @Nullable List<String> tags){
if (tags == null || tags.isEmpty()) return false; if(tags==null || tags.isEmpty()) return false;
editText.setText(tags); editText.setText(tags);
editText.chipifyAllUnterminatedTokens(); editText.chipifyAllUnterminatedTokens();
return true; return true;
} }
private NachoTextView prepareChipTextView(NachoTextView nacho) { private NachoTextView prepareChipTextView(NachoTextView nacho){
//Ill Be Back //Ill Be Back
nacho.setChipTerminators( nacho.setChipTerminators(
Map.of( Map.of(
@ -290,25 +290,25 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
) )
); );
nacho.enableEditChipOnTouch(true, true); nacho.enableEditChipOnTouch(true, true);
nacho.setOnFocusChangeListener((v, hasFocus) -> nacho.chipifyAllUnterminatedTokens()); nacho.setOnFocusChangeListener((v, hasFocus)->nacho.chipifyAllUnterminatedTokens());
return nacho; return nacho;
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove) { protected void makeTimelineEditor(@Nullable TimelineDefinition item, Consumer<TimelineDefinition> onSave, Runnable onRemove){
Context ctx = getContext(); Context ctx=getContext();
View view = getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false); View view=getActivity().getLayoutInflater().inflate(R.layout.edit_timeline, list, false);
View divider = view.findViewById(R.id.divider); View divider=view.findViewById(R.id.divider);
Button advancedBtn = view.findViewById(R.id.advanced); Button advancedBtn=view.findViewById(R.id.advanced);
EditText editText = view.findViewById(R.id.input); EditText editText=view.findViewById(R.id.input);
if (item != null) editText.setText(item.getCustomTitle()); if(item!=null) editText.setText(item.getCustomTitle());
editText.setHint(item != null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag)); editText.setHint(item!=null ? item.getDefaultTitle(ctx) : ctx.getString(R.string.sk_hashtag));
LinearLayout tagWrap = view.findViewById(R.id.tag_wrap); LinearLayout tagWrap=view.findViewById(R.id.tag_wrap);
boolean advancedOptionsAvailable = item == null || item.getType() == TimelineDefinition.TimelineType.HASHTAG; boolean hashtagOptionsAvailable=item==null || item.getType()==TimelineDefinition.TimelineType.HASHTAG;
advancedBtn.setVisibility(advancedOptionsAvailable ? View.VISIBLE : View.GONE); advancedBtn.setVisibility(hashtagOptionsAvailable ? View.VISIBLE : View.GONE);
advancedBtn.setOnClickListener(l -> { advancedBtn.setOnClickListener(l->{
advancedBtn.setSelected(!advancedBtn.isSelected()); advancedBtn.setSelected(!advancedBtn.isSelected());
advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show); advancedBtn.setText(advancedBtn.isSelected() ? R.string.sk_advanced_options_hide : R.string.sk_advanced_options_show);
divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE); divider.setVisibility(advancedBtn.isSelected() ? View.VISIBLE : View.GONE);
@ -316,25 +316,25 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
UiUtils.beginLayoutTransition((ViewGroup) view); UiUtils.beginLayoutTransition((ViewGroup) view);
}); });
Switch localOnlySwitch = view.findViewById(R.id.local_only_switch); Switch localOnlySwitch=view.findViewById(R.id.local_only_switch);
view.findViewById(R.id.local_only) view.findViewById(R.id.local_only).setOnClickListener(l->localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
.setOnClickListener(l -> localOnlySwitch.setChecked(!localOnlySwitch.isChecked()));
EditText tagMain = view.findViewById(R.id.tag_main); EditText tagMain=view.findViewById(R.id.tag_main);
NachoTextView tagsAny = prepareChipTextView(view.findViewById(R.id.tags_any)); NachoTextView tagsAny=prepareChipTextView(view.findViewById(R.id.tags_any));
NachoTextView tagsAll = prepareChipTextView(view.findViewById(R.id.tags_all)); NachoTextView tagsAll=prepareChipTextView(view.findViewById(R.id.tags_all));
NachoTextView tagsNone = prepareChipTextView(view.findViewById(R.id.tags_none)); NachoTextView tagsNone=prepareChipTextView(view.findViewById(R.id.tags_none));
if (item != null) {
if(item!=null && hashtagOptionsAvailable){
tagMain.setText(item.getHashtagName()); tagMain.setText(item.getHashtagName());
boolean hasAdvanced = !TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle()); boolean hasAdvanced=!TextUtils.isEmpty(item.getCustomTitle()) && !Objects.equals(item.getHashtagName(), item.getCustomTitle());
hasAdvanced = setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced; hasAdvanced=setTagListContent(tagsAny, item.getHashtagAny()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced; hasAdvanced=setTagListContent(tagsAll, item.getHashtagAll()) || hasAdvanced;
hasAdvanced = setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced; hasAdvanced=setTagListContent(tagsNone, item.getHashtagNone()) || hasAdvanced;
if (item.isHashtagLocalOnly()) { if(item.isHashtagLocalOnly()){
localOnlySwitch.setChecked(true); localOnlySwitch.setChecked(true);
hasAdvanced = true; hasAdvanced=true;
} }
if (hasAdvanced) { if(hasAdvanced){
advancedBtn.setSelected(true); advancedBtn.setSelected(true);
advancedBtn.setText(R.string.sk_advanced_options_hide); advancedBtn.setText(R.string.sk_advanced_options_hide);
tagWrap.setVisibility(View.VISIBLE); tagWrap.setVisibility(View.VISIBLE);
@ -342,58 +342,62 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
} }
ImageButton btn = view.findViewById(R.id.button); ImageButton btn=view.findViewById(R.id.button);
PopupMenu popup = new PopupMenu(ctx, btn); PopupMenu popup=new PopupMenu(ctx, btn);
TimelineDefinition.Icon currentIcon = item != null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG; TimelineDefinition.Icon currentIcon=item!=null ? item.getIcon() : TimelineDefinition.Icon.HASHTAG;
btn.setImageResource(currentIcon.iconRes); btn.setImageResource(currentIcon.iconRes);
btn.setTag(currentIcon.ordinal()); btn.setTag(currentIcon.ordinal());
btn.setContentDescription(ctx.getString(currentIcon.nameRes)); btn.setContentDescription(ctx.getString(currentIcon.nameRes));
btn.setOnTouchListener(popup.getDragToOpenListener()); btn.setOnTouchListener(popup.getDragToOpenListener());
btn.setOnClickListener(l -> popup.show()); btn.setOnClickListener(l->popup.show());
Menu menu = popup.getMenu(); Menu menu=popup.getMenu();
TimelineDefinition.Icon defaultIcon = item != null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG; TimelineDefinition.Icon defaultIcon=item!=null ? item.getDefaultIcon() : TimelineDefinition.Icon.HASHTAG;
menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes); menu.add(0, currentIcon.ordinal(), NONE, currentIcon.nameRes).setIcon(currentIcon.iconRes);
if (!currentIcon.equals(defaultIcon)) { if(!currentIcon.equals(defaultIcon)){
menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes); menu.add(0, defaultIcon.ordinal(), NONE, defaultIcon.nameRes).setIcon(defaultIcon.iconRes);
} }
for (TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()) { for(TimelineDefinition.Icon icon : TimelineDefinition.Icon.values()){
if (icon.hidden || icon.ordinal() == (int) btn.getTag()) continue; if(icon.hidden || icon.ordinal()==(int) btn.getTag()) continue;
menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes); menu.add(0, icon.ordinal(), NONE, icon.nameRes).setIcon(icon.iconRes);
} }
UiUtils.enablePopupMenuIcons(ctx, popup); UiUtils.enablePopupMenuIcons(ctx, popup);
popup.setOnMenuItemClickListener(menuItem -> { popup.setOnMenuItemClickListener(menuItem->{
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[menuItem.getItemId()]; TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[menuItem.getItemId()];
btn.setImageResource(icon.iconRes); btn.setImageResource(icon.iconRes);
btn.setTag(menuItem.getItemId()); btn.setTag(menuItem.getItemId());
btn.setContentDescription(ctx.getString(icon.nameRes)); btn.setContentDescription(ctx.getString(icon.nameRes));
return true; return true;
}); });
AlertDialog.Builder builder = new M3AlertDialogBuilder(ctx) AlertDialog.Builder builder=new M3AlertDialogBuilder(ctx)
.setTitle(item == null ? R.string.sk_add_timeline : R.string.sk_edit_timeline) .setTitle(item==null ? R.string.sk_add_timeline : R.string.sk_edit_timeline)
.setView(view) .setView(view)
.setPositiveButton(R.string.save, (d, which) -> { .setPositiveButton(R.string.save, (d, which)->{
String name=editText.getText().toString().trim();
String mainHashtag=tagMain.getText().toString().trim();
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tagsAny.chipifyAllUnterminatedTokens(); tagsAny.chipifyAllUnterminatedTokens();
tagsAll.chipifyAllUnterminatedTokens(); tagsAll.chipifyAllUnterminatedTokens();
tagsNone.chipifyAllUnterminatedTokens(); tagsNone.chipifyAllUnterminatedTokens();
String name = editText.getText().toString().trim(); if(TextUtils.isEmpty(mainHashtag)){
String mainHashtag = tagMain.getText().toString().trim(); mainHashtag=name;
if (TextUtils.isEmpty(mainHashtag)) { name=null;
mainHashtag = name;
name = null;
} }
if (TextUtils.isEmpty(mainHashtag) && (item != null && item.getType() == TimelineDefinition.TimelineType.HASHTAG)) { if(TextUtils.isEmpty(mainHashtag) && (item!=null && item.getType()==TimelineDefinition.TimelineType.HASHTAG)){
Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show(); Toast.makeText(ctx, R.string.sk_add_timeline_tag_error_empty, Toast.LENGTH_SHORT).show();
onSave.accept(null); onSave.accept(null);
return; return;
} }
}
TimelineDefinition tl = item != null ? item : TimelineDefinition.ofHashtag(name); TimelineDefinition tl=item!=null ? item : TimelineDefinition.ofHashtag(name);
TimelineDefinition.Icon icon = TimelineDefinition.Icon.values()[(int) btn.getTag()]; TimelineDefinition.Icon icon=TimelineDefinition.Icon.values()[(int) btn.getTag()];
tl.setIcon(icon); tl.setIcon(icon);
tl.setTitle(name); tl.setTitle(name);
if(item.getType()==TimelineDefinition.TimelineType.HASHTAG){
tl.setTagOptions( tl.setTagOptions(
mainHashtag, mainHashtag,
tagsAny.getChipValues(), tagsAny.getChipValues(),
@ -401,11 +405,12 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
tagsNone.getChipValues(), tagsNone.getChipValues(),
localOnlySwitch.isChecked() localOnlySwitch.isChecked()
); );
}
onSave.accept(tl); onSave.accept(tl);
}) })
.setNegativeButton(R.string.cancel, (d, which) -> {}); .setNegativeButton(R.string.cancel, (d, which)->{});
if (onRemove != null) builder.setNeutralButton(R.string.sk_remove, (d, which) -> onRemove.run()); if(onRemove!=null) builder.setNeutralButton(R.string.sk_remove, (d, which)->onRemove.run());
builder.show(); builder.show();
btn.requestFocus(); btn.requestFocus();
@ -419,12 +424,12 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
@Override @Override
public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position) { public void onBindViewHolder(@NonNull TimelineViewHolder holder, int position){
holder.bind(data.get(position)); holder.bind(data.get(position));
} }
@Override @Override
public int getItemCount() { public int getItemCount(){
return data.size(); return data.size();
} }
} }
@ -441,12 +446,12 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onBind(TimelineDefinition item) { public void onBind(TimelineDefinition item){
title.setText(item.getTitle(getContext())); title.setText(item.getTitle(getContext()));
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null); title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(item.getIcon().iconRes), null, null, null);
dragger.setVisibility(View.VISIBLE); dragger.setVisibility(View.VISIBLE);
dragger.setOnTouchListener((View v, MotionEvent event) -> { dragger.setOnTouchListener((View v, MotionEvent event)->{
if (event.getAction() == MotionEvent.ACTION_DOWN) { if(event.getAction()==MotionEvent.ACTION_DOWN){
itemTouchHelper.startDrag(this); itemTouchHelper.startDrag(this);
return true; return true;
} }
@ -454,34 +459,34 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
}); });
} }
private void onSave(TimelineDefinition tl) { private void onSave(TimelineDefinition tl){
saveTimelines(); saveTimelines();
rebind(); rebind();
} }
private void onRemove() { private void onRemove(){
removeTimeline(getAbsoluteAdapterPosition()); removeTimeline(getAbsoluteAdapterPosition());
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@Override @Override
public void onClick() { public void onClick(){
makeTimelineEditor(item, this::onSave, this::onRemove); makeTimelineEditor(item, this::onSave, this::onRemove);
} }
} }
private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback { private class ItemTouchHelperCallback extends ItemTouchHelper.SimpleCallback{
public ItemTouchHelperCallback() { public ItemTouchHelperCallback(){
super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); super(ItemTouchHelper.UP|ItemTouchHelper.DOWN, ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT);
} }
@Override @Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target){
int fromPosition = viewHolder.getAbsoluteAdapterPosition(); int fromPosition=viewHolder.getAbsoluteAdapterPosition();
int toPosition = target.getAbsoluteAdapterPosition(); int toPosition=target.getAbsoluteAdapterPosition();
if (Math.max(fromPosition, toPosition) >= data.size() || Math.min(fromPosition, toPosition) < 0) { if(Math.max(fromPosition, toPosition)>=data.size() || Math.min(fromPosition, toPosition)<0){
return false; return false;
} else { }else{
Collections.swap(data, fromPosition, toPosition); Collections.swap(data, fromPosition, toPosition);
adapter.notifyItemMoved(fromPosition, toPosition); adapter.notifyItemMoved(fromPosition, toPosition);
saveTimelines(); saveTimelines();
@ -490,21 +495,21 @@ public class EditTimelinesFragment extends MastodonRecyclerFragment<TimelineDefi
} }
@Override @Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) { public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState){
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG && viewHolder != null) { if(actionState==ItemTouchHelper.ACTION_STATE_DRAG && viewHolder!=null){
viewHolder.itemView.animate().alpha(0.65f); viewHolder.itemView.animate().alpha(0.65f);
} }
} }
@Override @Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder){
super.clearView(recyclerView, viewHolder); super.clearView(recyclerView, viewHolder);
viewHolder.itemView.animate().alpha(1f); viewHolder.itemView.animate().alpha(1f);
} }
@Override @Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction){
int position = viewHolder.getAbsoluteAdapterPosition(); int position=viewHolder.getAbsoluteAdapterPosition();
removeTimeline(position); removeTimeline(position);
} }
} }

View file

@ -229,7 +229,6 @@ public class NotificationsListFragment extends BaseStatusListFragment<Notificati
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState){ public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
list.addItemDecoration(new RecyclerView.ItemDecoration(){ list.addItemDecoration(new RecyclerView.ItemDecoration(){
private Paint paint=new Paint(); private Paint paint=new Paint();
private Rect tmpRect=new Rect(); private Rect tmpRect=new Rect();

View file

@ -21,9 +21,12 @@ import android.graphics.drawable.LayerDrawable;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ImageSpan; import android.text.style.ImageSpan;
import android.text.TextWatcher;
import android.transition.ChangeBounds; import android.transition.ChangeBounds;
import android.transition.Fade; import android.transition.Fade;
import android.transition.TransitionManager; import android.transition.TransitionManager;
@ -149,7 +152,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private SwipeRefreshLayout refreshLayout; private SwipeRefreshLayout refreshLayout;
private View followersBtn, followingBtn; private View followersBtn, followingBtn;
private EditText nameEdit, bioEdit; private EditText nameEdit, bioEdit;
private ProgressBar actionProgress, notifyProgress; private ProgressBar actionProgress, notifyProgress, noteSaveProgress;
private FrameLayout[] tabViews; private FrameLayout[] tabViews;
private TabLayoutMediator tabLayoutMediator; private TabLayoutMediator tabLayoutMediator;
private TextView followsYouView; private TextView followsYouView;
@ -160,9 +163,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private View actionButtonWrap; private View actionButtonWrap;
private CustomDrawingOrderLinearLayout scrollableContent; private CustomDrawingOrderLinearLayout scrollableContent;
public FrameLayout noteWrap;
public EditText noteEdit;
private String note;
private Account account, remoteAccount; private Account account, remoteAccount;
private String accountID; private String accountID;
private String domain; private String domain;
@ -193,6 +193,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback()); private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback());
private ListImageLoaderWrapper imgLoader; private ListImageLoaderWrapper imgLoader;
// profile note
private FrameLayout noteWrap;
private ImageButton noteSaveBtn;
private EditText noteEdit;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -262,9 +267,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
bioEdit=content.findViewById(R.id.bio_edit); bioEdit=content.findViewById(R.id.bio_edit);
nameEditWrap=content.findViewById(R.id.name_edit_wrap); nameEditWrap=content.findViewById(R.id.name_edit_wrap);
bioEditWrap=content.findViewById(R.id.bio_edit_wrap); bioEditWrap=content.findViewById(R.id.bio_edit_wrap);
usernameWrap=content.findViewById(R.id.username_wrap);
actionProgress=content.findViewById(R.id.action_progress); actionProgress=content.findViewById(R.id.action_progress);
notifyProgress=content.findViewById(R.id.notify_progress); notifyProgress=content.findViewById(R.id.notify_progress);
noteSaveProgress=content.findViewById(R.id.note_save_progress);
fab=content.findViewById(R.id.fab); fab=content.findViewById(R.id.fab);
followsYouView=content.findViewById(R.id.follows_you); followsYouView=content.findViewById(R.id.follows_you);
countersLayout=content.findViewById(R.id.profile_counters); countersLayout=content.findViewById(R.id.profile_counters);
@ -279,60 +284,50 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
avatar.setOutlineProvider(OutlineProviders.roundedRect(24)); avatar.setOutlineProvider(OutlineProviders.roundedRect(24));
avatar.setClipToOutline(true); avatar.setClipToOutline(true);
noteEdit = content.findViewById(R.id.note_edit); noteEdit=content.findViewById(R.id.note_edit);
noteWrap = content.findViewById(R.id.note_edit_wrap); noteWrap=content.findViewById(R.id.note_edit_wrap);
ImageButton noteEditConfirm = content.findViewById(R.id.note_edit_confirm); noteSaveBtn=content.findViewById(R.id.note_save_btn);
noteEditConfirm.setOnClickListener((v -> { noteSaveBtn.setOnClickListener((v->{
if (!noteEdit.getText().toString().trim().equals(note)) { savePrivateNote(noteEdit.getText().toString());
savePrivateNote(); InputMethodManager imm=(InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
}
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0); imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0);
noteEdit.clearFocus(); noteEdit.clearFocus();
noteSaveBtn.clearFocus();
})); }));
noteEdit.setOnFocusChangeListener((v, hasFocus) -> { noteEdit.setOnFocusChangeListener((v, hasFocus)->{
if (hasFocus) { if(hasFocus){
fab.setVisibility(View.INVISIBLE); hideFab();
TranslateAnimation animate = new TranslateAnimation( V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
0, noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
0, }else if(!noteSaveBtn.hasFocus()){
0, showFab();
fab.getHeight() * 2); hideNoteSaveBtnIfNotDirty();
animate.setDuration(300);
fab.startAnimation(animate);
noteEditConfirm.setVisibility(View.VISIBLE);
noteEditConfirm.animate()
.alpha(1.0f)
.setDuration(700);
} else {
fab.setVisibility(View.VISIBLE);
TranslateAnimation animate = new TranslateAnimation(
0,
0,
fab.getHeight() * 2,
0);
animate.setDuration(300);
fab.startAnimation(animate);
noteEditConfirm.animate()
.alpha(0.0f)
.setDuration(700);
noteEditConfirm.setVisibility(View.INVISIBLE);
} }
}); });
noteEditConfirm.setOnClickListener((v -> { noteEdit.addTextChangedListener(new TextWatcher(){
if (!noteEdit.getText().toString().trim().equals(note)) { @Override
savePrivateNote(); public void beforeTextChanged(CharSequence s, int start, int count, int after){}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count){
if(relationship!=null && noteSaveBtn.getVisibility()!=View.VISIBLE && !s.toString().equals(relationship.note))
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
} }
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(this.getView().getRootView().getWindowToken(), 0); @Override
noteEdit.clearFocus(); public void afterTextChanged(Editable s){}
})); });
noteSaveBtn.setOnFocusChangeListener((v, hasFocus)->{
if(!hasFocus && !noteEdit.hasFocus()){
showFab();
hideNoteSaveBtnIfNotDirty();
}
});
FrameLayout sizeWrapper=new FrameLayout(getActivity()){ FrameLayout sizeWrapper=new FrameLayout(getActivity()){
@Override @Override
@ -498,6 +493,46 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return sizeWrapper; return sizeWrapper;
} }
private void hideNoteSaveBtnIfNotDirty(){
if(noteEdit.getText().toString().equals(relationship.note)){
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
}
}
private void showPrivateNote(){
noteWrap.setVisibility(View.VISIBLE);
noteEdit.setText(relationship.note);
}
private void hidePrivateNote(){
noteWrap.setVisibility(View.GONE);
noteEdit.setText(null);
}
private void savePrivateNote(String note){
if(note!=null && note.equals(relationship.note)){
updateRelationship();
invalidateOptionsMenu();
return;
}
V.setVisibilityAnimated(noteSaveProgress, View.VISIBLE);
V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
new SetPrivateNote(profileAccountID, note).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {
updateRelationship(result);
invalidateOptionsMenu();
}
@Override
public void onError(ErrorResponse error) {
error.showToast(getContext());
V.setVisibilityAnimated(noteSaveProgress, View.GONE);
V.setVisibilityAnimated(noteSaveBtn, View.VISIBLE);
}
}).exec(accountID);
}
private void onAccountLoaded(Account result) { private void onAccountLoaded(Account result) {
account=result; account=result;
isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account); isOwnProfile=AccountSessionManager.getInstance().isSelf(accountID, account);
@ -524,25 +559,6 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
V.setVisibilityAnimated(fab, View.VISIBLE); V.setVisibilityAnimated(fab, View.VISIBLE);
} }
public void setNote(String note){
this.note=note;
noteWrap.setVisibility(View.VISIBLE);
noteEdit.setVisibility(View.VISIBLE);
noteEdit.setText(note);
}
private void savePrivateNote(){
new SetPrivateNote(profileAccountID, noteEdit.getText().toString()).setCallback(new Callback<>() {
@Override
public void onSuccess(Relationship result) {}
@Override
public void onError(ErrorResponse error) {
error.showToast(getActivity());
}
}).exec(accountID);
}
@Override @Override
protected void doLoadData(){ protected void doLoadData(){
if (remoteAccount != null) { if (remoteAccount != null) {
@ -884,6 +900,8 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else{ }else{
blockDomain.setVisible(false); blockDomain.setVisible(false);
} }
menu.findItem(R.id.edit_note).setTitle(noteWrap.getVisibility()==View.GONE && (relationship.note==null || relationship.note.isEmpty())
? R.string.sk_add_note : R.string.sk_delete_note);
} }
@Override @Override
@ -948,12 +966,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
final Bundle args=new Bundle(); final Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account)); args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), MutesListFragment.class, args); Nav.go(getActivity(), MutedAccountsListFragment.class, args);
}else if(id==R.id.blocked_accounts){ }else if(id==R.id.blocked_accounts){
final Bundle args=new Bundle(); final Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
args.putParcelable("targetAccount", Parcels.wrap(account)); args.putParcelable("targetAccount", Parcels.wrap(account));
Nav.go(getActivity(), BlocksListFragment.class, args); Nav.go(getActivity(), BlockedAccountsListFragment.class, args);
}else if(id==R.id.followed_hashtags){ }else if(id==R.id.followed_hashtags){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@ -965,6 +983,26 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}else if(id==R.id.save){ }else if(id==R.id.save){
if(isInEditMode) if(isInEditMode)
saveAndExitEditMode(); saveAndExitEditMode();
}else if(id==R.id.edit_note){
if(noteWrap.getVisibility()==View.GONE){
showPrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
noteEdit.requestFocus();
noteEdit.postDelayed(()->{
InputMethodManager imm=getActivity().getSystemService(InputMethodManager.class);
imm.showSoftInput(noteEdit, 0);
}, 100);
}else if(relationship.note.isEmpty()){
hidePrivateNote();
UiUtils.beginLayoutTransition(scrollableContent);
}else{
new M3AlertDialogBuilder(getActivity())
.setMessage(getContext().getString(R.string.sk_private_note_confirm_delete, account.getDisplayUsername()))
.setPositiveButton(R.string.delete, (dlg, btn)->savePrivateNote(null))
.setNegativeButton(R.string.cancel, null)
.show();
}
invalidateOptionsMenu();
} }
return true; return true;
} }
@ -990,20 +1028,22 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
private void updateRelationship(){ private void updateRelationship(){
if(getActivity()==null) return; if(getActivity()==null) return;
if(relationship.note!=null && !relationship.note.isEmpty()) showPrivateNote();
else hidePrivateNote();
invalidateOptionsMenu(); invalidateOptionsMenu();
actionButton.setVisibility(View.VISIBLE); actionButton.setVisibility(View.VISIBLE);
notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE); notifyButton.setVisibility(relationship.following ? View.VISIBLE : View.GONE);
UiUtils.setRelationshipToActionButtonM3(relationship, actionButton); UiUtils.setRelationshipToActionButtonM3(relationship, actionButton);
actionProgress.setIndeterminateTintList(actionButton.getTextColors()); actionProgress.setIndeterminateTintList(actionButton.getTextColors());
notifyProgress.setIndeterminateTintList(notifyButton.getTextColors()); notifyProgress.setIndeterminateTintList(notifyButton.getTextColors());
noteSaveProgress.setIndeterminateTintList(noteEdit.getTextColors());
followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE); followsYouView.setVisibility(relationship.followedBy ? View.VISIBLE : View.GONE);
notifyButton.setSelected(relationship.notifying); notifyButton.setSelected(relationship.notifying);
notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username)); notifyButton.setContentDescription(getString(relationship.notifying ? R.string.sk_user_post_notifications_on : R.string.sk_user_post_notifications_off, '@'+account.username));
noteEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
if (!isOwnProfile) { V.setVisibilityAnimated(noteSaveProgress, View.GONE);
setNote(relationship.note); V.setVisibilityAnimated(noteSaveBtn, View.INVISIBLE);
// aboutFragment.setNote(relationship.note, accountID, profileAccountID); UiUtils.beginLayoutTransition(scrollableContent);
}
} }
public ImageButton getFab() { public ImageButton getFab() {
@ -1315,7 +1355,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
@Override @Override
public boolean onBackPressed(){ public boolean onBackPressed(){
if(noteEdit.hasFocus()) { if(noteEdit.hasFocus()) {
savePrivateNote(); savePrivateNote(noteEdit.getText().toString());
} }
if(isInEditMode){ if(isInEditMode){
if(savingEdits) if(savingEdits)
@ -1576,7 +1616,7 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
title.setText(item.parsedName); title.setText(item.parsedName);
value.setText(item.parsedValue); value.setText(item.parsedValue);
if(item.verifiedAt!=null){ if(item.verifiedAt!=null){
int textColor=UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63; int textColor=UiUtils.getThemeColor(getContext(), R.attr.colorM3Success);
value.setTextColor(textColor); value.setTextColor(textColor);
value.setLinkTextColor(textColor); value.setLinkTextColor(textColor);
Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate(); Drawable check=getResources().getDrawable(R.drawable.ic_fluent_checkmark_starburst_20_regular, getActivity().getTheme()).mutate();

View file

@ -12,6 +12,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration; import org.joinmastodon.android.ui.utils.InsetStatusItemDecoration;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
@ -86,7 +87,8 @@ public class StatusEditHistoryFragment extends StatusListFragment{
EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class); EnumSet<StatusEditChangeType> changes=EnumSet.noneOf(StatusEditChangeType.class);
Status prev=data.get(idx+1); Status prev=data.get(idx+1);
if(!Objects.equals(s.content, prev.content)){ // if only formatting was changed, don't even try to create a diff text
if(!Objects.equals(HtmlParser.text(s.content), HtmlParser.text(prev.content))){
changes.add(StatusEditChangeType.TEXT_CHANGED); changes.add(StatusEditChangeType.TEXT_CHANGED);
//update status content to display a diffs //update status content to display a diffs
s.content=createDiffText(prev.content, s.content); s.content=createDiffText(prev.content, s.content);
@ -156,12 +158,6 @@ public class StatusEditHistoryFragment extends StatusListFragment{
return items; return items;
} }
@Override
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
list.addItemDecoration(new InsetStatusItemDecoration(this));
}
@Override @Override
public boolean isItemEnabled(String id){ public boolean isItemEnabled(String id){
return false; return false;
@ -178,24 +174,24 @@ public class StatusEditHistoryFragment extends StatusListFragment{
} }
private String createDiffText(String original, String modified) { private String createDiffText(String original, String modified) {
diff_match_patch dmp = new diff_match_patch(); diff_match_patch dmp=new diff_match_patch();
LinkedList<diff_match_patch.Diff> diffs = dmp.diff_main(original, modified); LinkedList<diff_match_patch.Diff> diffs=dmp.diff_main(original, modified);
dmp.diff_cleanupSemantic(diffs); dmp.diff_cleanupSemantic(diffs);
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder=new StringBuilder();
for(diff_match_patch.Diff diff: diffs){ for(diff_match_patch.Diff diff : diffs){
switch(diff.operation){ switch(diff.operation){
case DELETE -> { case DELETE->{
stringBuilder.append("<edit_diff_removed>"); stringBuilder.append("<edit-diff-delete>");
stringBuilder.append(diff.text); stringBuilder.append(diff.text);
stringBuilder.append("</edit_diff_removed>"); stringBuilder.append("</edit-diff-delete>");
} }
case INSERT -> { case INSERT->{
stringBuilder.append("<edit_diff_added>"); stringBuilder.append("<edit-diff-insert>");
stringBuilder.append(diff.text); stringBuilder.append(diff.text);
stringBuilder.append("</edit_diff_added>"); stringBuilder.append("</edit-diff-insert>");
} }
default -> stringBuilder.append(diff.text); default->stringBuilder.append(diff.text);
} }
} }
return stringBuilder.toString(); return stringBuilder.toString();

View file

@ -89,8 +89,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
@Override @Override
public void onItemClick(String id){ public void onItemClick(String id){
Status status=getContentStatusByID(id); Status status=getContentStatusByID(id);
if(status==null) if(status==null || status.preview) return;
return;
if(status.isRemote){ if(status.isRemote){
UiUtils.lookupStatus(getContext(), status, accountID, null, status1 -> { UiUtils.lookupStatus(getContext(), status, accountID, null, status1 -> {
status1.filterRevealed = true; status1.filterRevealed = true;
@ -392,6 +391,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
Status contentStatus=status.getContentStatus(); Status contentStatus=status.getContentStatus();
if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){ if(contentStatus.poll!=null && contentStatus.poll.id.equals(ev.poll.id)){
updatePoll(status.id, contentStatus, ev.poll); updatePoll(status.id, contentStatus, ev.poll);
AccountSessionManager.get(accountID).getCacheController().updateStatus(contentStatus);
} }
} }
} }

View file

@ -54,21 +54,25 @@ import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent { public class ThreadFragment extends StatusListFragment implements ProvidesAssistContent {
protected Status mainStatus, updatedStatus; protected Status mainStatus, updatedStatus, replyTo;
private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>(); private final HashMap<String, NeighborAncestryInfo> ancestryMap = new HashMap<>();
private StatusContext result; private StatusContext result;
protected boolean contextInitiallyRendered, transitionFinished; protected boolean contextInitiallyRendered, transitionFinished, preview;
@Override @Override
public void onCreate(Bundle savedInstanceState){ public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
mainStatus=Parcels.unwrap(getArguments().getParcelable("status")); mainStatus=Parcels.unwrap(getArguments().getParcelable("status"));
replyTo=Parcels.unwrap(getArguments().getParcelable("inReplyTo"));
Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount")); Account inReplyToAccount=Parcels.unwrap(getArguments().getParcelable("inReplyToAccount"));
refreshing=contextInitiallyRendered=getArguments().getBoolean("refresh", false);
if(inReplyToAccount!=null) if(inReplyToAccount!=null)
knownAccounts.put(inReplyToAccount.id, inReplyToAccount); knownAccounts.put(inReplyToAccount.id, inReplyToAccount);
data.add(mainStatus); data.add(mainStatus);
onAppendItems(Collections.singletonList(mainStatus)); onAppendItems(Collections.singletonList(mainStatus));
setTitle(HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis)); preview=mainStatus.preview;
if(preview) setRefreshEnabled(false);
setTitle(preview ? getString(R.string.sk_post_preview) : HtmlParser.parseCustomEmoji(getString(R.string.post_from_user, mainStatus.account.getDisplayName()), mainStatus.account.emojis));
transitionFinished = getArguments().getBoolean("noTransition", false); transitionFinished = getArguments().getBoolean("noTransition", false);
E.register(this); E.register(this);
@ -155,11 +159,21 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
if (refreshing) loadMainStatus(); if(preview && replyTo==null){
currentRequest=new GetStatusContext(mainStatus.id) result=new StatusContext();
result.descendants=Collections.emptyList();
result.ancestors=Collections.emptyList();
return;
}
if(refreshing && !preview) loadMainStatus();
currentRequest=new GetStatusContext(preview ? replyTo.id : mainStatus.id)
.setCallback(new SimpleCallback<>(this){ .setCallback(new SimpleCallback<>(this){
@Override @Override
public void onSuccess(StatusContext result){ public void onSuccess(StatusContext result){
if(preview){
result.descendants=Collections.emptyList();
result.ancestors.add(replyTo);
}
ThreadFragment.this.result = result; ThreadFragment.this.result = result;
maybeApplyContext(); maybeApplyContext();
} }

View file

@ -1,8 +1,6 @@
package org.joinmastodon.android.fragments.report; package org.joinmastodon.android.fragments.report;
import android.app.Activity; import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -25,6 +23,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.CheckableHeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.DummyStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.LinkCardStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.MediaGridStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@ -97,8 +96,7 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
.exec(accountID); .exec(accountID);
} }
@Override public void onToggleItem(String id){
public void onItemClick(String id){
if(selectedIDs.contains(id)) if(selectedIDs.contains(id))
selectedIDs.remove(id); selectedIDs.remove(id);
else else
@ -121,15 +119,22 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view); RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder) if(holder.getAbsoluteAdapterPosition()==0 || holder instanceof CheckableHeaderStatusDisplayItem.Holder)
return; return;
outRect.left=V.dp(40); boolean isRTL=parent.getLayoutDirection()==View.LAYOUT_DIRECTION_RTL;
if(isRTL) outRect.right=V.dp(40);
else outRect.left=V.dp(40);
if(holder instanceof AudioStatusDisplayItem.Holder){ if(holder instanceof AudioStatusDisplayItem.Holder){
outRect.bottom=V.dp(16); outRect.bottom=V.dp(16);
}else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){ }else if(holder instanceof LinkCardStatusDisplayItem.Holder || holder instanceof MediaGridStatusDisplayItem.Holder){
outRect.bottom=V.dp(16); outRect.bottom=V.dp(8);
if(isRTL){
outRect.right+=V.dp(16);
outRect.left=V.dp(16);
}else{
outRect.left+=V.dp(16); outRect.left+=V.dp(16);
outRect.right=V.dp(16); outRect.right=V.dp(16);
} }
} }
}
}); });
ProgressBar topProgress=view.findViewById(R.id.top_progress); ProgressBar topProgress=view.findViewById(R.id.top_progress);
@ -155,9 +160,6 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
return adapter; return adapter;
} }
protected void drawDivider(View child, View bottomSibling, RecyclerView.ViewHolder holder, RecyclerView.ViewHolder siblingHolder, RecyclerView parent, Canvas c, Paint paint){
}
private void onButtonClick(View v){ private void onButtonClick(View v){
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", accountID); args.putString("account", accountID);
@ -201,7 +203,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
@Override @Override
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_INSET | StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN); List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, getFilterContext(), StatusDisplayItem.FLAG_NO_FOOTER | StatusDisplayItem.FLAG_CHECKABLE | StatusDisplayItem.FLAG_MEDIA_FORCE_HIDDEN);
items.add(new DummyStatusDisplayItem(s.getID(), this));
return items;
} }
@Override @Override

View file

@ -204,14 +204,6 @@ public class ReportReasonChoiceFragment extends StatusListFragment{
float off=paint.getStrokeWidth()/2f; float off=paint.getStrokeWidth()/2f;
c.drawRoundRect(V.dp(16)-off, top-off, parent.getWidth()-V.dp(16)+off, bottom+off, V.dp(12), V.dp(12), paint); c.drawRoundRect(V.dp(16)-off, top-off, parent.getWidth()-V.dp(16)+off, bottom+off, V.dp(12), V.dp(12), paint);
} }
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state){
RecyclerView.ViewHolder holder=parent.getChildViewHolder(view);
if(holder instanceof StatusDisplayItem.Holder<?>){
outRect.left=outRect.right=V.dp(16);
}
}
}); });
} }
} }

View file

@ -2,20 +2,39 @@ package org.joinmastodon.android.fragments.settings;
import android.app.Activity; import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.BuildConfig;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.MastodonAPIController;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.HasAccountID; import org.joinmastodon.android.fragments.HasAccountID;
import org.joinmastodon.android.model.viewmodel.CheckableListItem;
import org.joinmastodon.android.model.viewmodel.ListItem; import org.joinmastodon.android.model.viewmodel.ListItem;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.updater.GithubSelfUpdater;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -26,10 +45,13 @@ import me.grishka.appkit.utils.MergeRecyclerAdapter;
import me.grishka.appkit.utils.SingleViewRecyclerAdapter; import me.grishka.appkit.utils.SingleViewRecyclerAdapter;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> implements HasAccountID{ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void>{
private ListItem<Void> mediaCacheItem; private static final String TAG="SettingsAboutAppFragment";
private ListItem<Void> mediaCacheItem, copyCrashLogItem;
private CheckableListItem<Void> enablePreReleasesItem;
private AccountSession session; private AccountSession session;
private boolean timelineCacheCleared=false; private boolean timelineCacheCleared=false;
private File crashLogFile=new File(MastodonApp.context.getFilesDir(), "crash.log");
// MOSHIDON // MOSHIDON
private ListItem<Void> clearRecentEmojisItem; private ListItem<Void> clearRecentEmojisItem;
@ -38,22 +60,35 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTitle(getString(R.string.about_app, getString(R.string.mo_app_name))); setTitle(getString(R.string.about_app, getString(R.string.mo_app_name)));
session=AccountSessionManager.get(accountID); session=AccountSessionManager.get(accountID);
onDataLoaded(List.of(
String lastModified=crashLogFile.exists()
? DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT).withZone(ZoneId.systemDefault()).format(Instant.ofEpochMilli(crashLogFile.lastModified()))
: getString(R.string.sk_settings_crash_log_unavailable);
List<ListItem<Void>> items=new ArrayList<>(List.of(
new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.mo_donate_url))), new ListItem<>(R.string.sk_settings_donate, 0, R.drawable.ic_fluent_heart_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.mo_donate_url))),
new ListItem<>(R.string.mo_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.mo_repo_url))), new ListItem<>(R.string.mo_settings_contribute, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.mo_repo_url))),
new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")), new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")),
new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true), new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true),
clearRecentEmojisItem=new ListItem<>(R.string.mo_clear_recent_emoji, 0, this::onClearRecentEmojisClick), clearRecentEmojisItem=new ListItem<>(R.string.mo_clear_recent_emoji, 0, this::onClearRecentEmojisClick),
mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick), mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick),
new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick) new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick),
copyCrashLogItem=new ListItem<>(getString(R.string.sk_settings_copy_crash_log), lastModified, 0, this::onCopyCrashLog)
)); ));
if(GithubSelfUpdater.needSelfUpdating()){
items.add(enablePreReleasesItem=new CheckableListItem<>(R.string.sk_updater_enable_pre_releases, 0, CheckableListItem.Style.SWITCH, GlobalUserPreferences.enablePreReleases, i->toggleCheckableItem(enablePreReleasesItem)));
}
copyCrashLogItem.isEnabled=crashLogFile.exists();
onDataLoaded(items);
updateMediaCacheItem(); updateMediaCacheItem();
} }
@Override @Override
protected void onHidden(){ protected void onHidden(){
super.onHidden(); super.onHidden();
GlobalUserPreferences.enablePreReleases=enablePreReleasesItem!=null && enablePreReleasesItem.checked;
GlobalUserPreferences.save();
if(timelineCacheCleared) getActivity().recreate(); if(timelineCacheCleared) getActivity().recreate();
} }
@ -110,4 +145,17 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment<Void> impleme
public String getAccountID(){ public String getAccountID(){
return accountID; return accountID;
} }
private void onCopyCrashLog(ListItem<?> item){
if(!crashLogFile.exists()) return;
try(InputStream is=new FileInputStream(crashLogFile)){
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
StringBuilder sb=new StringBuilder();
String line;
while ((line=reader.readLine())!=null) sb.append(line).append("\n");
UiUtils.copyText(list, sb.toString());
} catch(IOException e){
Log.e(TAG, "Error reading crash log", e);
}
}
} }

View file

@ -333,13 +333,15 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
return; return;
} }
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()) {
UnifiedPush.unregisterApp( UnifiedPush.unregisterApp(
getContext(), getContext(),
accountID accountSession.getID()
); );
//re-register to fcm //re-register to fcm
AccountSessionManager.getInstance().getAccount(accountID).getPushSubscriptionManager().registerAccountForPush(getPushSubscription()); accountSession.getPushSubscriptionManager().registerAccountForPush(getPushSubscription());
}
unifiedPushItem.toggle(); unifiedPushItem.toggle();
rebindItem(unifiedPushItem); rebindItem(unifiedPushItem);
} }
@ -349,12 +351,14 @@ public class SettingsNotificationsFragment extends BaseSettingsFragment<Void>{
(dialog, which)->{ (dialog, which)->{
String userDistrib = distributors.get(which); String userDistrib = distributors.get(which);
UnifiedPush.saveDistributor(getContext(), userDistrib); UnifiedPush.saveDistributor(getContext(), userDistrib);
for (AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){
UnifiedPush.registerApp( UnifiedPush.registerApp(
getContext(), getContext(),
accountID, accountSession.getID(),
new ArrayList<>(), new ArrayList<>(),
getContext().getPackageName() getContext().getPackageName()
); );
}
unifiedPushItem.toggle(); unifiedPushItem.toggle();
rebindItem(unifiedPushItem); rebindItem(unifiedPushItem);
}).setOnCancelListener(d->rebindItem(unifiedPushItem)).show(); }).setOnCancelListener(d->rebindItem(unifiedPushItem)).show();

View file

@ -0,0 +1,14 @@
package org.joinmastodon.android.model;
public class AkkomaTranslation extends BaseModel{
public String text;
public String detectedLanguage;
public Translation toTranslation() {
Translation translation=new Translation();
translation.content=text;
translation.detectedSourceLanguage=detectedLanguage;
translation.provider="Akkoma";
return translation;
}
}

View file

@ -161,11 +161,15 @@ public class Instance extends BaseModel{
case BUBBLE_TIMELINE -> pleromaFeatures case BUBBLE_TIMELINE -> pleromaFeatures
.map(f -> f.contains("bubble_timeline")) .map(f -> f.contains("bubble_timeline"))
.orElse(false); .orElse(false);
case MACHINE_TRANSLATION -> pleromaFeatures
.map(f -> f.contains("akkoma:machine_translation"))
.orElse(false);
}; };
} }
public enum Feature { public enum Feature {
BUBBLE_TIMELINE BUBBLE_TIMELINE,
MACHINE_TRANSLATION
} }
@Parcel @Parcel

View file

@ -6,6 +6,7 @@ import static org.joinmastodon.android.api.MastodonAPIController.gsonWithoutDese
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import org.joinmastodon.android.api.ObjectValidationException; import org.joinmastodon.android.api.ObjectValidationException;
import org.joinmastodon.android.api.RequiredField; import org.joinmastodon.android.api.RequiredField;
@ -101,6 +102,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public transient TranslationState translationState=TranslationState.HIDDEN; public transient TranslationState translationState=TranslationState.HIDDEN;
public transient Translation translation; public transient Translation translation;
public transient boolean fromStatusCreated; public transient boolean fromStatusCreated;
public transient boolean preview;
public Status(){} public Status(){}
@ -128,6 +130,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
if(filtered!=null) if(filtered!=null)
for(FilterResult fr:filtered) for(FilterResult fr:filtered)
fr.postprocess(); fr.postprocess();
if(quote!=null)
quote.postprocess();
spoilerRevealed=!hasSpoiler(); spoilerRevealed=!hasSpoiler();
if(!spoilerRevealed) sensitive=true; if(!spoilerRevealed) sensitive=true;
@ -226,16 +230,17 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48"); public static final Pattern BOTTOM_TEXT_PATTERN = Pattern.compile("(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️)(?:\uD83D\uDC49\uD83D\uDC48(?:[\uD83E\uDEC2\uD83D\uDC96✨\uD83E\uDD7A,]+|❤️))*\uD83D\uDC49\uD83D\uDC48");
public boolean isEligibleForTranslation(AccountSession session){ public boolean isEligibleForTranslation(AccountSession session){
Instance instanceInfo = AccountSessionManager.getInstance().getInstanceInfo(session.domain); Instance instanceInfo=AccountSessionManager.getInstance().getInstanceInfo(session.domain);
boolean translateEnabled = instanceInfo != null && boolean translateEnabled=instanceInfo!=null && (
instanceInfo.v2 != null && instanceInfo.v2.configuration.translation != null && (instanceInfo.v2!=null && instanceInfo.v2.configuration.translation!=null && instanceInfo.v2.configuration.translation.enabled) ||
instanceInfo.v2.configuration.translation.enabled; (instanceInfo.isAkkoma() && instanceInfo.hasFeature(Instance.Feature.MACHINE_TRANSLATION))
);
try { try {
String bottomText = BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find() Pair<String, List<String>> decoded=BOTTOM_TEXT_PATTERN.matcher(getStrippedText()).find()
? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN) ? new StatusTextEncoder(Bottom::decode).decode(getStrippedText(), BOTTOM_TEXT_PATTERN)
: null; : null;
if(bottomText==null || bottomText.length()==0 || bottomText.equals("\u0005")) bottomText=null; String bottomText=decoded==null || decoded.second.stream().allMatch(s->s.trim().isEmpty()) ? null : decoded.first;
if(bottomText!=null){ if(bottomText!=null){
translation=new Translation(); translation=new Translation();
translation.content=bottomText; translation.content=bottomText;
@ -272,7 +277,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
s.visibility=StatusPrivacy.PUBLIC; s.visibility=StatusPrivacy.PUBLIC;
s.reactions=List.of(); s.reactions=List.of();
s.mentions=List.of(); s.mentions=List.of();
s.tags =List.of(); s.tags=List.of();
s.emojis=List.of(); s.emojis=List.of();
s.filtered=List.of(); s.filtered=List.of();
return s; return s;

View file

@ -14,6 +14,8 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.CustomLocalTimelineFragment; import org.joinmastodon.android.fragments.CustomLocalTimelineFragment;
import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSession;
import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BookmarkedStatusListFragment;
import org.joinmastodon.android.fragments.FavoritedStatusListFragment;
import org.joinmastodon.android.fragments.HashtagTimelineFragment; import org.joinmastodon.android.fragments.HashtagTimelineFragment;
import org.joinmastodon.android.fragments.HomeTimelineFragment; import org.joinmastodon.android.fragments.HomeTimelineFragment;
import org.joinmastodon.android.fragments.ListTimelineFragment; import org.joinmastodon.android.fragments.ListTimelineFragment;
@ -147,6 +149,8 @@ public class TimelineDefinition {
case LIST -> listTitle; case LIST -> listTitle;
case HASHTAG -> hashtagName; case HASHTAG -> hashtagName;
case BUBBLE -> ctx.getString(R.string.sk_timeline_bubble); case BUBBLE -> ctx.getString(R.string.sk_timeline_bubble);
case BOOKMARKS -> ctx.getString(R.string.bookmarks);
case FAVORITES -> ctx.getString(R.string.your_favorites);
case CUSTOM_LOCAL_TIMELINE -> domain; case CUSTOM_LOCAL_TIMELINE -> domain;
}; };
} }
@ -161,6 +165,8 @@ public class TimelineDefinition {
case HASHTAG -> Icon.HASHTAG; case HASHTAG -> Icon.HASHTAG;
case CUSTOM_LOCAL_TIMELINE -> Icon.CUSTOM_LOCAL_TIMELINE; case CUSTOM_LOCAL_TIMELINE -> Icon.CUSTOM_LOCAL_TIMELINE;
case BUBBLE -> Icon.BUBBLE; case BUBBLE -> Icon.BUBBLE;
case BOOKMARKS -> Icon.BOOKMARKS;
case FAVORITES -> Icon.FAVORITES;
}; };
} }
@ -174,6 +180,8 @@ public class TimelineDefinition {
case POST_NOTIFICATIONS -> new NotificationsListFragment(); case POST_NOTIFICATIONS -> new NotificationsListFragment();
case BUBBLE -> new BubbleTimelineFragment(); case BUBBLE -> new BubbleTimelineFragment();
case CUSTOM_LOCAL_TIMELINE -> new CustomLocalTimelineFragment(); case CUSTOM_LOCAL_TIMELINE -> new CustomLocalTimelineFragment();
case BOOKMARKS -> new BookmarkedStatusListFragment();
case FAVORITES -> new FavoritedStatusListFragment();
}; };
} }
@ -244,7 +252,19 @@ public class TimelineDefinition {
return args; return args;
} }
public enum TimelineType { HOME, LOCAL, FEDERATED, POST_NOTIFICATIONS, LIST, HASHTAG, CUSTOM_LOCAL_TIMELINE, BUBBLE } public enum TimelineType {
HOME,
LOCAL,
FEDERATED,
POST_NOTIFICATIONS,
LIST,
HASHTAG,
BUBBLE,
// not really timelines, but some people want it, so,,
BOOKMARKS,
FAVORITES
}
public enum Icon { public enum Icon {
HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart), HEART(R.drawable.ic_fluent_heart_24_regular, R.string.sk_icon_heart),
@ -309,6 +329,13 @@ public class TimelineDefinition {
DOCTOR(R.drawable.ic_fluent_doctor_24_regular, R.string.sk_icon_doctor), DOCTOR(R.drawable.ic_fluent_doctor_24_regular, R.string.sk_icon_doctor),
DIAMOND(R.drawable.ic_fluent_premium_24_regular, R.string.sk_icon_diamond), DIAMOND(R.drawable.ic_fluent_premium_24_regular, R.string.sk_icon_diamond),
UMBRELLA(R.drawable.ic_fluent_umbrella_24_regular, R.string.sk_icon_umbrella), UMBRELLA(R.drawable.ic_fluent_umbrella_24_regular, R.string.sk_icon_umbrella),
WATER(R.drawable.ic_fluent_water_24_regular, R.string.sk_icon_water),
SUN(R.drawable.ic_fluent_weather_sunny_24_regular, R.string.sk_icon_sun),
SUNSET(R.drawable.ic_fluent_weather_sunny_low_24_regular, R.string.sk_icon_sunset),
CLOUD(R.drawable.ic_fluent_cloud_24_regular, R.string.sk_icon_cloud),
THUNDERSTORM(R.drawable.ic_fluent_weather_thunderstorm_24_regular, R.string.sk_icon_thunderstorm),
RAIN(R.drawable.ic_fluent_weather_rain_24_regular, R.string.sk_icon_rain),
SNOWFLAKE(R.drawable.ic_fluent_weather_snowflake_24_regular, R.string.sk_icon_snowflake),
HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true), HOME(R.drawable.ic_fluent_home_24_regular, R.string.sk_timeline_home, true),
LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true), LOCAL(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
@ -318,7 +345,9 @@ public class TimelineDefinition {
EXCLUSIVE_LIST(R.drawable.ic_fluent_rss_24_regular, R.string.sk_exclusive_list, true), EXCLUSIVE_LIST(R.drawable.ic_fluent_rss_24_regular, R.string.sk_exclusive_list, true),
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true), HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true),
CUSTOM_LOCAL_TIMELINE(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true), CUSTOM_LOCAL_TIMELINE(R.drawable.ic_fluent_people_community_24_regular, R.string.sk_timeline_local, true),
BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true); BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true),
BOOKMARKS(R.drawable.ic_fluent_bookmark_multiple_24_regular, R.string.bookmarks, true),
FAVORITES(R.drawable.ic_fluent_star_24_regular, R.string.your_favorites, true);
public final int iconRes, nameRes; public final int iconRes, nameRes;
public final boolean hidden; public final boolean hidden;
@ -338,6 +367,8 @@ public class TimelineDefinition {
public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL); public static final TimelineDefinition LOCAL_TIMELINE = new TimelineDefinition(TimelineType.LOCAL);
public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED); public static final TimelineDefinition FEDERATED_TIMELINE = new TimelineDefinition(TimelineType.FEDERATED);
public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS); public static final TimelineDefinition POSTS_TIMELINE = new TimelineDefinition(TimelineType.POST_NOTIFICATIONS);
public static final TimelineDefinition BOOKMARKS_TIMELINE = new TimelineDefinition(TimelineType.BOOKMARKS);
public static final TimelineDefinition FAVORITES_TIMELINE = new TimelineDefinition(TimelineType.FAVORITES);
public static final TimelineDefinition BUBBLE_TIMELINE = new TimelineDefinition(TimelineType.BUBBLE) { public static final TimelineDefinition BUBBLE_TIMELINE = new TimelineDefinition(TimelineType.BUBBLE) {
@Override @Override
public boolean isCompatible(AccountSession session) { public boolean isCompatible(AccountSession session) {
@ -382,6 +413,8 @@ public class TimelineDefinition {
LOCAL_TIMELINE, LOCAL_TIMELINE,
FEDERATED_TIMELINE, FEDERATED_TIMELINE,
POSTS_TIMELINE, POSTS_TIMELINE,
BUBBLE_TIMELINE BUBBLE_TIMELINE,
BOOKMARKS_TIMELINE,
FAVORITES_TIMELINE
); );
} }

View file

@ -32,7 +32,6 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class AudioStatusDisplayItem extends StatusDisplayItem{ public class AudioStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final Attachment attachment; public final Attachment attachment;
private final ImageLoaderRequest imageRequest; private final ImageLoaderRequest imageRequest;

View file

@ -3,13 +3,13 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity; import android.app.Activity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.CheckBox; import android.widget.CheckBox;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.report.ReportAddPostsChoiceFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Notification;
import org.joinmastodon.android.model.ScheduledStatus;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.views.CheckableRelativeLayout; import org.joinmastodon.android.ui.views.CheckableRelativeLayout;
@ -34,8 +34,16 @@ public class CheckableHeaderStatusDisplayItem extends HeaderStatusDisplayItem{
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_header_checkable, parent); super(activity, R.layout.display_item_header_checkable, parent);
checkbox=findViewById(R.id.checkbox); checkbox=findViewById(R.id.checkbox);
view=(CheckableRelativeLayout) itemView; view=findViewById(R.id.checkbox_wrap);
checkbox.setBackground(new CheckBox(activity).getButtonDrawable()); checkbox.setBackground(new CheckBox(activity).getButtonDrawable());
view.setOnClickListener(this::onToggle);
view.setAccessibilityDelegate(new View.AccessibilityDelegate(){
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info){
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(CheckBox.class.getName());
}
});
} }
@Override @Override
@ -46,6 +54,12 @@ public class CheckableHeaderStatusDisplayItem extends HeaderStatusDisplayItem{
} }
} }
private void onToggle(View v){
if(item.parentFragment instanceof ReportAddPostsChoiceFragment reportFragment){
reportFragment.onToggleItem(item.parentID);
}
}
public void setIsChecked(Predicate<Holder> isChecked){ public void setIsChecked(Predicate<Holder> isChecked){
this.isChecked=isChecked; this.isChecked=isChecked;
} }

View file

@ -58,7 +58,6 @@ import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem { public class EmojiReactionsStatusDisplayItem extends StatusDisplayItem {
public final Status status;
private final Drawable placeholder; private final Drawable placeholder;
private final boolean hideEmpty, forAnnouncement, playGifs; private final boolean hideEmpty, forAnnouncement, playGifs;
private final String accountID; private final String accountID;

View file

@ -37,7 +37,6 @@ import androidx.annotation.PluralsRes;
import me.grishka.appkit.Nav; import me.grishka.appkit.Nav;
public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final String accountID; public final String accountID;
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT); private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
@ -131,6 +130,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
} }
private void startAccountListFragment(Class<? extends StatusRelatedAccountListFragment> cls){ private void startAccountListFragment(Class<? extends StatusRelatedAccountListFragment> cls){
if(item.status.preview) return;
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID()); args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("status", Parcels.wrap(item.status)); args.putParcelable("status", Parcels.wrap(item.status));
@ -138,6 +138,7 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
} }
private void startEditHistoryFragment(){ private void startEditHistoryFragment(){
if(item.status.preview) return;
Bundle args=new Bundle(); Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID()); args.putString("account", item.parentFragment.getAccountID());
args.putString("id", item.status.id); args.putString("id", item.status.id);

View file

@ -176,6 +176,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private boolean onButtonTouch(View v, MotionEvent event){ private boolean onButtonTouch(View v, MotionEvent event){
if(item.status.preview) return false;
boolean disabled = !v.isEnabled() || (v instanceof FrameLayout parentFrame && boolean disabled = !v.isEnabled() || (v instanceof FrameLayout parentFrame &&
parentFrame.getChildCount() > 0 && !parentFrame.getChildAt(0).isEnabled()); parentFrame.getChildCount() > 0 && !parentFrame.getChildAt(0).isEnabled());
int action = event.getAction(); int action = event.getAction();
@ -198,6 +199,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onReplyClick(View v){ private void onReplyClick(View v){
if(item.status.preview) return;
if(item.status.isRemote){ if(item.status.isRemote){
UiUtils.lookupStatus(v.getContext(), UiUtils.lookupStatus(v.getContext(),
item.status, item.accountID, null, item.status, item.accountID, null,
@ -219,6 +221,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private boolean onReplyLongClick(View v) { private boolean onReplyLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false; if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickAccount(v.getContext(), item.accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> { UiUtils.pickAccount(v.getContext(), item.accountID, R.string.sk_reply_as, R.drawable.ic_fluent_arrow_reply_28_regular, session -> {
Bundle args=new Bundle(); Bundle args=new Bundle();
@ -234,6 +237,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onBoostClick(View v){ private void onBoostClick(View v){
if(item.status.preview) return;
if (GlobalUserPreferences.confirmBoost) { if (GlobalUserPreferences.confirmBoost) {
UiUtils.opacityIn(v); UiUtils.opacityIn(v);
onBoostLongClick(v); onBoostLongClick(v);
@ -263,6 +267,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private boolean onBoostLongClick(View v){ private boolean onBoostLongClick(View v){
if(item.status.preview) return false;
Context ctx = itemView.getContext(); Context ctx = itemView.getContext();
View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null); View menu = LayoutInflater.from(ctx).inflate(R.layout.item_boost_menu, null);
Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create(); Dialog dialog = new M3AlertDialogBuilder(ctx).setView(menu).create();
@ -358,6 +363,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onFavoriteClick(View v){ private void onFavoriteClick(View v){
if(item.status.preview) return;
if(item.status.isRemote){ if(item.status.isRemote){
UiUtils.lookupStatus(v.getContext(), UiUtils.lookupStatus(v.getContext(),
item.status, item.accountID, null, item.status, item.accountID, null,
@ -389,6 +395,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private boolean onFavoriteLongClick(View v) { private boolean onFavoriteLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false; if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(), UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status, item.accountID, item.status,
@ -403,6 +410,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onBookmarkClick(View v){ private void onBookmarkClick(View v){
if(item.status.preview) return;
if(item.status.isRemote){ if(item.status.isRemote){
UiUtils.lookupStatus(v.getContext(), UiUtils.lookupStatus(v.getContext(),
item.status, item.accountID, null, item.status, item.accountID, null,
@ -426,6 +434,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private boolean onBookmarkLongClick(View v) { private boolean onBookmarkLongClick(View v) {
if(item.status.preview) return false;
if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false; if (AccountSessionManager.getInstance().getLoggedInAccounts().size() < 2) return false;
UiUtils.pickInteractAs(v.getContext(), UiUtils.pickInteractAs(v.getContext(),
item.accountID, item.status, item.accountID, item.status,
@ -440,6 +449,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private void onShareClick(View v){ private void onShareClick(View v){
if(item.status.preview) return;
UiUtils.opacityIn(v); UiUtils.opacityIn(v);
Intent intent=new Intent(Intent.ACTION_SEND); Intent intent=new Intent(Intent.ACTION_SEND);
intent.setType("text/plain"); intent.setType("text/plain");
@ -448,6 +458,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
} }
private boolean onShareLongClick(View v){ private boolean onShareLongClick(View v){
if(item.status.preview) return false;
UiUtils.copyText(v, item.status.url); UiUtils.copyText(v, item.status.url);
return true; return true;
} }

View file

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

View file

@ -78,7 +78,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
private String accountID; private String accountID;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper(); private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private SpannableStringBuilder parsedName; private SpannableStringBuilder parsedName;
public final Status status;
public boolean hasVisibilityToggle; public boolean hasVisibilityToggle;
boolean needBottomPadding; boolean needBottomPadding;
private CharSequence extraText; private CharSequence extraText;
@ -458,6 +457,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
} }
private void onMoreClick(View v){ private void onMoreClick(View v){
if(item.status.preview) return;
updateOptionsMenu(); updateOptionsMenu();
optionsMenu.show(); optionsMenu.show();
if(relationship==null && currentRelationshipRequest==null){ if(relationship==null && currentRelationshipRequest==null){

View file

@ -24,7 +24,6 @@ import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class LinkCardStatusDisplayItem extends StatusDisplayItem{ public class LinkCardStatusDisplayItem extends StatusDisplayItem{
private final Status status;
private final UrlImageLoaderRequest imgRequest; private final UrlImageLoaderRequest imgRequest;
public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, boolean showImagePreview){ public LinkCardStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, boolean showImagePreview){

View file

@ -61,7 +61,6 @@ public class MediaGridStatusDisplayItem extends StatusDisplayItem{
private final List<Attachment> attachments; private final List<Attachment> attachments;
private final Map<String, Pair<String, String>> translatedAttachments = new HashMap<>(); private final Map<String, Pair<String, String>> translatedAttachments = new HashMap<>();
private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>(); private final ArrayList<ImageLoaderRequest> requests=new ArrayList<>();
public final Status status;
public String sensitiveTitle; public String sensitiveTitle;
public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){ public MediaGridStatusDisplayItem(String parentID, BaseStatusListFragment<?> parentFragment, PhotoLayoutHelper.TiledLayoutResult tiledLayout, List<Attachment> attachments, Status status){

View file

@ -43,7 +43,6 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
public boolean needBottomPadding; public boolean needBottomPadding;
ReblogOrReplyLineStatusDisplayItem extra; ReblogOrReplyLineStatusDisplayItem extra;
CharSequence fullText; CharSequence fullText;
Status status;
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) { public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon, StatusPrivacy visibility, @Nullable View.OnClickListener handleClick, Status status) {
this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status); this(parentID, parentFragment, text, emojis, icon, visibility, handleClick, text, status);

View file

@ -24,7 +24,6 @@ import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class SpoilerStatusDisplayItem extends StatusDisplayItem{ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
public final Status status;
public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>(); public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>();
private final CharSequence parsedTitle; private final CharSequence parsedTitle;
private CharSequence translatedTitle; private CharSequence translatedTitle;

View file

@ -59,13 +59,15 @@ import me.grishka.appkit.views.UsableRecyclerView;
public abstract class StatusDisplayItem{ public abstract class StatusDisplayItem{
public final String parentID; public final String parentID;
public final BaseStatusListFragment<?> parentFragment; public final BaseStatusListFragment<?> parentFragment;
public Status status;
public boolean inset; public boolean inset;
public int index; public int index;
public boolean public boolean
hasDescendantNeighbor=false, hasDescendantNeighbor=false,
hasAncestoringNeighbor=false, hasAncestoringNeighbor=false,
isMainStatus=true, isMainStatus=true,
isDirectDescendant=false; isDirectDescendant=false,
isForQuote=false;
public static final int FLAG_INSET=1; public static final int FLAG_INSET=1;
public static final int FLAG_NO_FOOTER=1 << 1; public static final int FLAG_NO_FOOTER=1 << 1;
@ -74,7 +76,8 @@ public abstract class StatusDisplayItem{
public static final int FLAG_NO_HEADER=1 << 4; public static final int FLAG_NO_HEADER=1 << 4;
public static final int FLAG_NO_TRANSLATE=1 << 5; public static final int FLAG_NO_TRANSLATE=1 << 5;
public static final int FLAG_NO_EMOJI_REACTIONS=1 << 6; public static final int FLAG_NO_EMOJI_REACTIONS=1 << 6;
public static final int FLAG_NO_MEDIA_PREVIEW=1 << 7; public static final int FLAG_IS_FOR_QUOTE=1 << 7;
public static final int FLAG_NO_MEDIA_PREVIEW=1 << 8;
public void setAncestryInfo( public void setAncestryInfo(
boolean hasDescendantNeighbor, boolean hasDescendantNeighbor,
@ -238,27 +241,28 @@ public abstract class StatusDisplayItem{
if(statusForContent.hasSpoiler()){ if(statusForContent.hasSpoiler()){
if (AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed = true; if (AccountSessionManager.get(accountID).getLocalPreferences().revealCWs) statusForContent.spoilerRevealed = true;
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER); SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER);
if((flags & FLAG_IS_FOR_QUOTE)!=0){
for(StatusDisplayItem item:spoilerItem.contentItems){
item.isForQuote=true;
}
}
items.add(spoilerItem); items.add(spoilerItem);
contentItems=spoilerItem.contentItems; contentItems=spoilerItem.contentItems;
}else{ }else{
contentItems=items; contentItems=items;
} }
if (statusForContent.quote != null) { if(statusForContent.quote!=null) {
boolean hasQuoteInlineTag = statusForContent.content.contains("<span class=\"quote-inline\">"); int quoteInlineIndex=statusForContent.content.lastIndexOf("<span class=\"quote-inline\"><br/><br/>RE:");
if (!hasQuoteInlineTag) { if (quoteInlineIndex!=-1)
String quoteUrl = statusForContent.quote.url; statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
String quoteInline = String.format("<span class=\"quote-inline\">%sRE: <a href=\"%s\">%s</a></span>",
statusForContent.content.endsWith("</p>") ? "" : "<br/><br/>", quoteUrl, quoteUrl);
statusForContent.content += quoteInline;
}
} }
boolean hasSpoiler=!TextUtils.isEmpty(statusForContent.spoilerText); boolean hasSpoiler=!TextUtils.isEmpty(statusForContent.spoilerText);
if(!TextUtils.isEmpty(statusForContent.content)){ if(!TextUtils.isEmpty(statusForContent.content)){
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID); SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext());
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered); HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0); TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID, fragment.getContext()), fragment, statusForContent, (flags & FLAG_NO_TRANSLATE) != 0);
contentItems.add(text); contentItems.add(text);
}else if(!hasSpoiler && header!=null){ }else if(!hasSpoiler && header!=null){
header.needBottomPadding=true; header.needBottomPadding=true;
@ -276,9 +280,11 @@ public abstract class StatusDisplayItem{
} }
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
MediaGridStatusDisplayItem mediaGrid=new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent); MediaGridStatusDisplayItem mediaGrid=new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent);
if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0) if((flags & FLAG_MEDIA_FORCE_HIDDEN)!=0){
mediaGrid.sensitiveTitle=fragment.getString(R.string.media_hidden); mediaGrid.sensitiveTitle=fragment.getString(R.string.media_hidden);
else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia) statusForContent.sensitiveRevealed=false;
statusForContent.sensitive=true;
} else if(statusForContent.sensitive && AccountSessionManager.get(accountID).getLocalPreferences().revealCWs && !AccountSessionManager.get(accountID).getLocalPreferences().hideSensitiveMedia)
statusForContent.sensitiveRevealed=true; statusForContent.sensitiveRevealed=true;
contentItems.add(mediaGrid); contentItems.add(mediaGrid);
} }
@ -295,17 +301,23 @@ public abstract class StatusDisplayItem{
} }
} }
if(statusForContent.poll!=null){ if(statusForContent.poll!=null){
buildPollItems(parentID, fragment, statusForContent.poll, contentItems, statusForContent); buildPollItems(parentID, fragment, statusForContent.poll, status, contentItems, statusForContent);
} }
if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty()){ if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && statusForContent.quote==null){
contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags & FLAG_NO_MEDIA_PREVIEW)==0)); contentItems.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent, (flags & FLAG_NO_MEDIA_PREVIEW)==0));
} }
if(statusForContent.quote!=null && !(parentObject instanceof Notification)){
if(!statusForContent.mediaAttachments.isEmpty() && statusForContent.poll==null) // add spacing if immediately preceded by attachment
contentItems.add(new DummyStatusDisplayItem(parentID, fragment));
contentItems.addAll(buildItems(fragment, statusForContent.quote, accountID, parentObject, knownAccounts, filterContext, FLAG_NO_FOOTER | FLAG_INSET | FLAG_NO_EMOJI_REACTIONS | FLAG_IS_FOR_QUOTE));
}
if(contentItems!=items && statusForContent.spoilerRevealed){ if(contentItems!=items && statusForContent.spoilerRevealed){
items.addAll(contentItems); items.addAll(contentItems);
} }
AccountLocalPreferences lp=fragment.getLocalPrefs(); AccountLocalPreferences lp=fragment.getLocalPrefs();
if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && lp.emojiReactionsEnabled && if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled &&
(lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment)){ (lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) &&
statusForContent.reactions!=null){
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id); boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
boolean showAddButton=lp.showEmojiReactions==ALWAYS || isMainStatus; boolean showAddButton=lp.showEmojiReactions==ALWAYS || isMainStatus;
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false)); items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false));
@ -317,8 +329,9 @@ public abstract class StatusDisplayItem{
items.add(footer); items.add(footer);
} }
boolean inset=(flags & FLAG_INSET)!=0; boolean inset=(flags & FLAG_INSET)!=0;
boolean isForQuote=(flags & FLAG_IS_FOR_QUOTE)!=0;
// add inset dummy so last content item doesn't clip out of inset bounds // add inset dummy so last content item doesn't clip out of inset bounds
if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0){ if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0 && !isForQuote){
items.add(new DummyStatusDisplayItem(parentID, fragment)); items.add(new DummyStatusDisplayItem(parentID, fragment));
// in case we ever need the dummy to display a margin for the media grid again: // in case we ever need the dummy to display a margin for the media grid again:
// (i forgot why we apparently don't need this anymore) // (i forgot why we apparently don't need this anymore)
@ -330,12 +343,22 @@ public abstract class StatusDisplayItem{
items.add(gap=new GapStatusDisplayItem(parentID, fragment, status)); items.add(gap=new GapStatusDisplayItem(parentID, fragment, status));
int i=1; int i=1;
for(StatusDisplayItem item:items){ for(StatusDisplayItem item:items){
item.inset=inset; if(inset)
item.inset=true;
if(isForQuote){
item.status=statusForContent;
item.isForQuote=true;
}
item.index=i++; item.index=i++;
} }
if(items!=contentItems && !statusForContent.spoilerRevealed){ if(items!=contentItems && !statusForContent.spoilerRevealed){
for(StatusDisplayItem item:contentItems){ for(StatusDisplayItem item:contentItems){
item.inset=inset; if(inset)
item.inset=true;
if(isForQuote){
item.status=statusForContent;
item.isForQuote=true;
}
item.index=i++; item.index=i++;
} }
} }
@ -353,7 +376,7 @@ public abstract class StatusDisplayItem{
); );
} }
public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List<StatusDisplayItem> items, Status status){ public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List<StatusDisplayItem> items, Status status){
int i=0; int i=0;
for(Poll.Option opt:poll.options){ for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status)); items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));
@ -390,12 +413,15 @@ public abstract class StatusDisplayItem{
} }
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{ public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{
private Context context;
public Holder(View itemView){ public Holder(View itemView){
super(itemView); super(itemView);
} }
public Holder(Context context, int layout, ViewGroup parent){ public Holder(Context context, int layout, ViewGroup parent){
super(context, layout, parent); super(context, layout, parent);
this.context=context;
} }
public String getItemID(){ public String getItemID(){
@ -404,6 +430,16 @@ public abstract class StatusDisplayItem{
@Override @Override
public void onClick(){ public void onClick(){
if(item.isForQuote){
item.status.filterRevealed=true;
Bundle args=new Bundle();
args.putString("account", item.parentFragment.getAccountID());
args.putParcelable("status", Parcels.wrap(item.status.clone()));
args.putBoolean("refresh", true);
Nav.go((Activity) context, ThreadFragment.class, args);
return;
}
item.parentFragment.onItemClick(item.parentID); item.parentFragment.onItemClick(item.parentID);
} }
@ -435,13 +471,13 @@ public abstract class StatusDisplayItem{
public boolean isLastDisplayItemForStatus(){ public boolean isLastDisplayItemForStatus(){
return getNextVisibleDisplayItem() return getNextVisibleDisplayItem()
.map(n->!n.parentID.equals(item.parentID)) .map(next->!next.parentID.equals(item.parentID) || item.inset && !next.inset)
.orElse(true); .orElse(true);
} }
@Override @Override
public boolean isEnabled(){ public boolean isEnabled(){
return item.parentFragment.isItemEnabled(item.parentID); return item.parentFragment.isItemEnabled(item.parentID) || item.isForQuote;
} }
} }
} }

View file

@ -42,7 +42,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public boolean textSelectable; public boolean textSelectable;
public boolean reduceTopPadding; public boolean reduceTopPadding;
public boolean disableTranslate; public boolean disableTranslate;
public final Status status;
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){ public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
super(parentID, parentFragment); super(parentID, parentFragment);
@ -116,7 +115,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text); text.setText(item.text);
} }
text.setTextIsSelectable(false); text.setTextIsSelectable(false);
if(item.textSelectable) itemView.post(() -> text.setTextIsSelectable(true)); if(item.textSelectable && !item.isForQuote) itemView.post(() -> text.setTextIsSelectable(true));
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false); itemView.setClickable(false);
itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom()); itemView.setPadding(itemView.getPaddingLeft(), item.reduceTopPadding ? V.dp(6) : V.dp(12), itemView.getPaddingRight(), itemView.getPaddingBottom());
@ -127,8 +126,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null); StatusDisplayItem next=getNextVisibleDisplayItem().orElse(null);
if(next!=null && !next.parentID.equals(item.parentID)) next=null; if(next!=null && !next.parentID.equals(item.parentID)) next=null;
int bottomPadding=next instanceof FooterStatusDisplayItem ? V.dp(6) int bottomPadding=item.inset ? V.dp(12)
: item.inset ? V.dp(12) : next instanceof FooterStatusDisplayItem ? V.dp(6)
: (next instanceof EmojiReactionsStatusDisplayItem || next==null) ? 0 : (next instanceof EmojiReactionsStatusDisplayItem || next==null) ? 0
: V.dp(12); : V.dp(12);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding); itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
@ -195,7 +194,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public void updateTranslation(boolean updateText){ public void updateTranslation(boolean updateText){
if(item.status==null) if(item.status==null)
return; return;
boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession()); boolean translateEnabled=!item.disableTranslate && item.status.isEligibleForTranslation(item.parentFragment.getSession()) && !item.isForQuote;
if(translationFooter==null && translateEnabled){ if(translationFooter==null && translateEnabled){
translationFooter=translationFooterStub.inflate(); translationFooter=translationFooterStub.inflate();
translationInfo=findViewById(R.id.translation_info_text); translationInfo=findViewById(R.id.translation_info_text);
@ -214,8 +213,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null; String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language; String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
Locale locale=lang!=null ? Locale.forLanguageTag(lang) : null; Locale locale=lang!=null ? Locale.forLanguageTag(lang) : null;
translationButton.setText(locale!=null String displayLang=locale==null || locale.getDisplayLanguage().isBlank() ? lang : locale.getDisplayLanguage();
? item.parentFragment.getString(R.string.translate_post, locale.getDisplayLanguage()) translationButton.setText(displayLang!=null
? item.parentFragment.getString(R.string.translate_post, displayLang)
: item.parentFragment.getString(R.string.sk_translate_post)); : item.parentFragment.getString(R.string.sk_translate_post));
translationButton.setClickable(true); translationButton.setClickable(true);
translationButton.animate().alpha(1).setDuration(100).start(); translationButton.animate().alpha(1).setDuration(100).start();

View file

@ -18,7 +18,6 @@ import java.util.List;
// Mind the gap! // Mind the gap!
public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{ public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
public boolean loading; public boolean loading;
public final Status status;
public List<StatusDisplayItem> filteredItems; public List<StatusDisplayItem> filteredItems;
public LegacyFilter applyingFilter; public LegacyFilter applyingFilter;

View file

@ -3,21 +3,21 @@ package org.joinmastodon.android.ui.text;
import android.text.TextPaint; import android.text.TextPaint;
import android.text.style.CharacterStyle; import android.text.style.CharacterStyle;
import org.joinmastodon.android.ui.utils.UiUtils;
public class DiffRemovedSpan extends CharacterStyle { public class DiffRemovedSpan extends CharacterStyle {
private final String text; private final String text;
private final int color;
public DiffRemovedSpan(String text){ public DiffRemovedSpan(String text, int color){
this.text=text; this.text=text;
this.color=color;
} }
@Override @Override
public void updateDrawState(TextPaint tp) { public void updateDrawState(TextPaint tp) {
tp.setStrikeThruText(true); tp.setStrikeThruText(true);
tp.setColor(0xFFCA5B63); tp.setColor(color);
} }
public String getText() { public String getText() {

View file

@ -70,6 +70,10 @@ public class HtmlParser{
private HtmlParser(){} private HtmlParser(){}
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID){
return parse(source, emojis, mentions, tags, accountID, null);
}
/** /**
* Parse HTML and custom emoji into a spanned string for display. * Parse HTML and custom emoji into a spanned string for display.
* Supported tags: <ul> * Supported tags: <ul>
@ -82,7 +86,7 @@ public class HtmlParser{
* @param emojis Custom emojis that are present in source as <code>:code:</code> * @param emojis Custom emojis that are present in source as <code>:code:</code>
* @return a spanned string * @return a spanned string
*/ */
public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID){ public static SpannableStringBuilder parse(String source, List<Emoji> emojis, List<Mention> mentions, List<Hashtag> tags, String accountID, Context context){
class SpanInfo{ class SpanInfo{
public Object span; public Object span;
public int start; public int start;
@ -107,6 +111,9 @@ public class HtmlParser{
Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity())); Map<String, Hashtag> tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
final SpannableStringBuilder ssb=new SpannableStringBuilder(); final SpannableStringBuilder ssb=new SpannableStringBuilder();
int colorInsert=UiUtils.getThemeColor(context, R.attr.colorM3Success);
int colorDelete=UiUtils.getThemeColor(context, R.attr.colorM3Error);
Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){ Jsoup.parseBodyFragment(source).body().traverse(new NodeVisitor(){
private final ArrayList<SpanInfo> openSpans=new ArrayList<>(); private final ArrayList<SpanInfo> openSpans=new ArrayList<>();
@ -172,9 +179,9 @@ public class HtmlParser{
} }
case "code", "pre" -> openSpans.add(new SpanInfo(new TypefaceSpan("monospace"), ssb.length(), el)); case "code", "pre" -> openSpans.add(new SpanInfo(new TypefaceSpan("monospace"), ssb.length(), el));
case "blockquote" -> openSpans.add(new SpanInfo(new LeadingMarginSpan.Standard(V.dp(10)), ssb.length(), el)); case "blockquote" -> openSpans.add(new SpanInfo(new LeadingMarginSpan.Standard(V.dp(10)), ssb.length(), el));
//fake elements for the edit history diff view // fake elements for the edit history diff view
case "edit_diff_added" -> openSpans.add(new SpanInfo(new ForegroundColorSpan(UiUtils.isDarkTheme() ? 0xFF89bb9c : 0xFF5b8e63), ssb.length(), el)); case "edit-diff-insert" -> openSpans.add(new SpanInfo(new ForegroundColorSpan(colorInsert), ssb.length(), el));
case "edit_diff_removed" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text()), ssb.length(), el)); case "edit-diff-delete" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text(), colorDelete), ssb.length(), el));
} }
} }
} }

View file

@ -1720,12 +1720,14 @@ public class UiUtils {
"pronouns.page/" "pronouns.page/"
}; };
private static final Pattern trimPronouns=Pattern.compile("[^\\w*]*([\\w*].*[\\w*]|[\\w*])\\W*"); private static final String PRONOUN_CHARS="\\w*¿¡!?";
private static final Pattern trimPronouns=
Pattern.compile("[^"+PRONOUN_CHARS+"]*(["+PRONOUN_CHARS+"].*["+PRONOUN_CHARS+"]|["+PRONOUN_CHARS+"])\\W*");
private static String extractPronounsFromField(String localizedPronouns, AccountField field) { private static String extractPronounsFromField(String localizedPronouns, AccountField field) {
if(!field.name.toLowerCase().contains(localizedPronouns) && if(!field.name.toLowerCase().contains(localizedPronouns) &&
!field.name.toLowerCase().contains("pronouns")) return null; !field.name.toLowerCase().contains("pronouns")) return null;
String text=HtmlParser.text(field.value); String text=HtmlParser.text(field.value);
if(field.value.toLowerCase().contains("https://")){ if(text.toLowerCase().contains("https://")){
for(String pronounUrl : pronounsUrls){ for(String pronounUrl : pronounsUrls){
int index=text.indexOf(pronounUrl); int index=text.indexOf(pronounUrl);
int beginPronouns=index+pronounUrl.length(); int beginPronouns=index+pronounUrl.length();
@ -1744,13 +1746,20 @@ public class UiUtils {
Matcher matcher=trimPronouns.matcher(text); Matcher matcher=trimPronouns.matcher(text);
if(!matcher.find()) return null; if(!matcher.find()) return null;
String pronouns=matcher.group(1); String pronouns=matcher.group(1);
// crude fix to allow for pronouns like "it(/she)"
int missingClosingParens=0; // crude fix to allow for pronouns like "it(/she)" or "(de) sie/ihr"
int missingParens=0, missingBrackets=0;
for(char c : pronouns.toCharArray()){ for(char c : pronouns.toCharArray()){
if(c=='(') missingClosingParens++; if(c=='(') missingParens++;
if(c==')') missingClosingParens--; else if(c=='[') missingBrackets++;
else if(c==')') missingParens--;
else if(c==']') missingBrackets--;
} }
pronouns+=")".repeat(Math.max(0, missingClosingParens)); if(missingParens > 0) pronouns+=")".repeat(missingParens);
else if(missingParens < 0) pronouns="(".repeat(missingParens*-1)+pronouns;
if(missingBrackets > 0) pronouns+="]".repeat(missingBrackets);
else if(missingBrackets < 0) pronouns="[".repeat(missingBrackets*-1)+pronouns;
// if ends with an un-closed custom emoji // if ends with an un-closed custom emoji
if(pronouns.matches("^.*\\s+:[a-zA-Z_]+$")) pronouns+=':'; if(pronouns.matches("^.*\\s+:[a-zA-Z_]+$")) pronouns+=':';
return pronouns; return pronouns;

View file

@ -217,6 +217,7 @@ public class AccountViewHolder extends BindableViewHolder<AccountViewModel> impl
Menu menu=contextMenu.getMenu(); Menu menu=contextMenu.getMenu();
Account account=item.account; Account account=item.account;
menu.findItem(R.id.edit_note).setVisible(false);
menu.findItem(R.id.manage_user_lists).setTitle(fragment.getString(R.string.sk_lists_with_user, account.getShortUsername())); menu.findItem(R.id.manage_user_lists).setTitle(fragment.getString(R.string.sk_lists_with_user, account.getShortUsername()));
MenuItem mute=menu.findItem(R.id.mute); MenuItem mute=menu.findItem(R.id.mute);
mute.setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername())); mute.setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));

View file

@ -1,9 +1,9 @@
package org.joinmastodon.android.utils; package org.joinmastodon.android.utils;
import android.text.TextUtils; import android.util.Pair;
import org.joinmastodon.android.fragments.ComposeFragment;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -40,19 +40,22 @@ public class StatusTextEncoder {
} }
// prettiest almost-exact replica of a pretty function // prettiest almost-exact replica of a pretty function
public String decode(String content, Pattern regex) { public Pair<String, List<String>> decode(String content, Pattern regex) {
Matcher m = regex.matcher(content); Matcher m=regex.matcher(content);
StringBuilder decodedString = new StringBuilder(); StringBuilder decodedString=new StringBuilder();
int previousEnd = 0; List<String> decodedParts=new ArrayList<>();
int previousEnd=0;
while (m.find()) { while (m.find()) {
MatchResult res = m.toMatchResult(); MatchResult res=m.toMatchResult();
// everything before the match - do not decode // everything before the match - do not decode
decodedString.append(content.substring(previousEnd, res.start())); decodedString.append(content.substring(previousEnd, res.start()));
previousEnd = res.end(); previousEnd=res.end();
// the match - do decode // the match - do decode
decodedString.append(fn.apply(res.group())); String decoded=fn.apply(res.group());
decodedParts.add(decoded);
decodedString.append(decoded);
} }
decodedString.append(content.substring(previousEnd)); decodedString.append(content.substring(previousEnd));
return decodedString.toString(); return Pair.create(decodedString.toString(), decodedParts);
} }
} }

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 5.5c-2.413 0-4.383 1.9-4.495 4.285-0.019 0.4-0.349 0.715-0.75 0.715H6.5c-1.657 0-3 1.343-3 3s1.343 3 3 3h11c1.657 0 3-1.343 3-3s-1.343-3-3-3h-0.256c-0.4 0-0.73-0.315-0.749-0.715C16.383 7.4 14.413 5.5 12 5.5zM6.08 9.02C6.548 6.171 9.02 4 12 4s5.452 2.172 5.92 5.02C20.208 9.23 22 11.155 22 13.5c0 2.485-2.015 4.5-4.5 4.5h-11C4.015 18 2 15.985 2 13.5c0-2.344 1.792-4.269 4.08-4.48z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M11 15c0-0.35 0.06-0.687 0.171-1H4.253c-1.242 0-2.25 1.007-2.25 2.25v0.577c0 0.892 0.32 1.756 0.9 2.435 1.565 1.834 3.951 2.74 7.097 2.74 0.398 0 0.783-0.015 1.157-0.044C11.055 21.658 11 21.335 11 21v-0.534c-0.322 0.023-0.655 0.035-1 0.035-2.739 0-4.705-0.745-5.958-2.213-0.348-0.407-0.54-0.926-0.54-1.461v-0.578c0-0.413 0.336-0.749 0.75-0.749H11V15zM10 2.005c2.762 0 5 2.239 5 5s-2.238 5-5 5c-2.761 0-5-2.239-5-5s2.239-5 5-5zm0 1.5c-1.933 0-3.5 1.567-3.5 3.5s1.567 3.5 3.5 3.5 3.5-1.567 3.5-3.5-1.567-3.5-3.5-3.5zM12 15c0-1.104 0.896-2 2-2h7c1.105 0 2 0.896 2 2v6c0 1.105-0.895 2-2 2h-7c-1.104 0-2-0.895-2-2v-6zm2.5 1c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h6c0.277 0 0.5-0.224 0.5-0.5S20.777 16 20.5 16h-6zm0 3c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h6c0.277 0 0.5-0.224 0.5-0.5S20.777 19 20.5 19h-6z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M16.088 6.412c-0.072-0.093-0.15-0.182-0.234-0.266-0.312-0.313-0.693-0.55-1.113-0.69L13.363 5.01c-0.106-0.037-0.198-0.107-0.263-0.198C13.035 4.719 13 4.609 13 4.497c0-0.113 0.035-0.223 0.1-0.315 0.065-0.091 0.157-0.16 0.263-0.198l1.378-0.448c0.414-0.143 0.789-0.379 1.096-0.69 0.299-0.304 0.525-0.67 0.663-1.072l0.011-0.034 0.448-1.377c0.038-0.106 0.107-0.198 0.2-0.263 0.091-0.065 0.2-0.1 0.313-0.1 0.113 0 0.223 0.035 0.315 0.1s0.161 0.157 0.199 0.263l0.447 1.377c0.14 0.418 0.375 0.798 0.687 1.11 0.312 0.312 0.693 0.547 1.111 0.686l1.378 0.448 0.028 0.007c0.106 0.037 0.198 0.107 0.263 0.198 0.065 0.092 0.1 0.202 0.1 0.314 0 0.113-0.035 0.223-0.1 0.315-0.065 0.091-0.157 0.16-0.263 0.198l-1.378 0.448c-0.419 0.139-0.8 0.374-1.112 0.686-0.312 0.311-0.547 0.692-0.686 1.11l-0.448 1.377L18 8.671c-0.04 0.092-0.104 0.171-0.186 0.23C17.722 8.964 17.613 9 17.5 9c-0.113 0-0.222-0.035-0.314-0.1s-0.162-0.157-0.2-0.263L16.54 7.26c-0.101-0.307-0.254-0.593-0.45-0.848zm7.695 3.801l-0.766-0.248c-0.232-0.078-0.444-0.208-0.617-0.381-0.173-0.174-0.304-0.385-0.381-0.617l-0.25-0.765c-0.02-0.06-0.059-0.11-0.11-0.146C21.61 8.018 21.547 8 21.485 8c-0.063 0-0.124 0.02-0.175 0.056-0.051 0.036-0.09 0.087-0.11 0.146l-0.25 0.764c-0.075 0.231-0.203 0.442-0.374 0.615-0.17 0.173-0.379 0.304-0.609 0.384l-0.765 0.248c-0.06 0.021-0.11 0.06-0.147 0.11C19.02 10.376 19 10.437 19 10.499c0 0.063 0.02 0.124 0.055 0.175 0.037 0.05 0.088 0.09 0.147 0.11l0.765 0.249c0.233 0.077 0.445 0.208 0.619 0.382 0.173 0.174 0.303 0.386 0.38 0.62l0.249 0.764c0.02 0.06 0.06 0.11 0.11 0.146C21.376 12.982 21.437 13 21.5 13c0.063 0 0.124-0.02 0.175-0.056 0.05-0.036 0.09-0.087 0.11-0.146l0.249-0.764c0.077-0.233 0.208-0.444 0.381-0.618 0.174-0.173 0.385-0.303 0.618-0.38l0.765-0.25c0.06-0.02 0.11-0.059 0.147-0.11C23.98 10.626 24 10.564 24 10.502c0-0.063-0.02-0.124-0.055-0.175-0.037-0.05-0.088-0.09-0.147-0.11l-0.015-0.004zM5.25 3h7.797c-0.306 0.11-0.573 0.308-0.767 0.569C12.098 3.834 12 4.148 12 4.469V4.5H5.25C4.836 4.5 4.5 4.836 4.5 5.25v12.5c0 0.966 0.784 1.75 1.75 1.75h9.25V7.327c0.034 0.072 0.065 0.146 0.09 0.222L16 8.999c0.106 0.31 0.305 0.579 0.57 0.77 0.133 0.092 0.278 0.162 0.43 0.21V14h4v3.75c0 1.795-1.455 3.25-3.25 3.25H6.25C4.455 21 3 19.545 3 17.75V5.25C3 4.007 4.007 3 5.25 3zM17 19.5h0.75c0.966 0 1.75-0.784 1.75-1.75V15.5H17v4zM7.25 7C6.836 7 6.5 7.336 6.5 7.75S6.836 8.5 7.25 8.5h5.5c0.414 0 0.75-0.336 0.75-0.75S13.164 7 12.75 7h-5.5zM6.5 11.75C6.5 11.336 6.836 11 7.25 11h5.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-5.5c-0.414 0-0.75-0.336-0.75-0.75zM7.25 15c-0.414 0-0.75 0.336-0.75 0.75s0.336 0.75 0.75 0.75h3c0.414 0 0.75-0.336 0.75-0.75S10.664 15 10.25 15h-3z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M18.38 4c0.31 0 0.588 0.191 0.7 0.482 0.56 1.462 1.609 2.015 2.17 2.015 0.414 0 0.75 0.335 0.75 0.75 0 0.414-0.336 0.75-0.75 0.75-1.002 0-2.087-0.611-2.873-1.687-0.814 1.093-1.971 1.687-3.187 1.687-1.217 0-2.376-0.595-3.19-1.69-0.814 1.095-1.973 1.69-3.19 1.69-1.215 0-2.373-0.593-3.187-1.686C4.838 7.388 3.753 8 2.75 8 2.336 8 2 7.664 2 7.25S2.336 6.5 2.75 6.5c0.56 0 1.61-0.553 2.17-2.018C5.031 4.192 5.31 4.001 5.62 4c0.311 0 0.59 0.192 0.701 0.482 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.596 2.489-2.015C11.41 4.192 11.69 4 12 4c0.31 0 0.59 0.192 0.7 0.482 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.597 2.489-2.015C17.79 4.192 18.069 4 18.379 4zm0 6c0.31 0 0.588 0.191 0.7 0.482 0.56 1.462 1.609 2.015 2.17 2.015 0.414 0 0.75 0.335 0.75 0.75 0 0.414-0.336 0.75-0.75 0.75-1.002 0-2.087-0.611-2.873-1.687-0.814 1.093-1.971 1.687-3.187 1.687-1.217 0-2.376-0.595-3.19-1.69-0.814 1.095-1.973 1.69-3.19 1.69-1.215 0-2.373-0.593-3.187-1.685C4.838 13.388 3.753 14 2.75 14 2.336 14 2 13.664 2 13.25s0.336-0.75 0.75-0.75c0.56 0 1.61-0.553 2.17-2.018 0.111-0.29 0.39-0.481 0.7-0.481 0.311 0 0.59 0.191 0.701 0.481 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.596 2.489-2.015 0.11-0.29 0.39-0.481 0.7-0.481 0.31 0 0.59 0.191 0.7 0.481 0.544 1.419 1.57 2.015 2.49 2.015 0.92 0 1.945-0.597 2.489-2.015 0.111-0.29 0.39-0.482 0.7-0.482zm0.7 6.482C18.969 16.192 18.69 16 18.38 16c-0.311 0-0.59 0.192-0.701 0.482-0.544 1.418-1.57 2.015-2.49 2.015-0.92 0-1.945-0.596-2.489-2.015C12.59 16.192 12.31 16 12 16c-0.31 0-0.59 0.192-0.7 0.482-0.544 1.419-1.57 2.015-2.49 2.015-0.92 0-1.945-0.596-2.489-2.015C6.21 16.192 5.931 16 5.621 16c-0.311 0-0.59 0.192-0.7 0.482C4.358 17.947 3.31 18.5 2.75 18.5 2.336 18.5 2 18.836 2 19.25S2.336 20 2.75 20c1.003 0 2.088-0.612 2.873-1.689 0.814 1.093 1.972 1.686 3.187 1.686 1.217 0 2.376-0.595 3.19-1.69 0.814 1.096 1.973 1.69 3.19 1.69 1.216 0 2.373-0.594 3.187-1.687 0.786 1.076 1.87 1.687 2.873 1.687 0.414 0 0.75-0.336 0.75-0.75 0-0.415-0.336-0.75-0.75-0.75-0.561 0-1.61-0.553-2.17-2.015z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 4.001c3.168 0 4.966 2.097 5.227 4.63h0.08c2.04 0 3.692 1.649 3.692 3.683 0 2.033-1.653 3.682-3.692 3.682h-0.582l-1.582 2.635c-0.207 0.358-0.666 0.481-1.025 0.274-0.329-0.19-0.46-0.591-0.32-0.933l0.046-0.091 1.149-1.885h-2.136l-1.582 2.635c-0.207 0.358-0.666 0.481-1.025 0.274-0.329-0.19-0.46-0.591-0.32-0.933l0.046-0.091 1.148-1.885H8.988l-1.582 2.635c-0.207 0.358-0.665 0.481-1.024 0.274-0.329-0.19-0.46-0.591-0.32-0.933l0.045-0.091 1.148-1.885H6.693C4.653 15.996 3 14.347 3 12.314 3 10.28 4.654 8.63 6.693 8.63h0.08C7.035 6.081 8.83 4.001 12 4.001zm0 1.498c-2.071 0-3.877 1.633-3.877 3.889 0 0.357-0.319 0.638-0.684 0.638H6.75c-1.261 0-2.284 1.001-2.284 2.236 0 1.235 1.022 2.237 2.284 2.237h10.5c1.261 0 2.284-1.002 2.284-2.237s-1.023-2.236-2.284-2.236h-0.69c-0.365 0-0.684-0.28-0.684-0.638 0-2.285-1.806-3.89-3.877-3.89z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M11.75 2c0.414 0 0.75 0.336 0.75 0.75v3.155l2.133-2.134c0.293-0.292 0.768-0.292 1.061 0 0.293 0.293 0.293 0.768 0 1.061L12.5 8.026V11h2.974l3.194-3.194c0.293-0.293 0.768-0.293 1.06 0 0.293 0.293 0.293 0.768 0 1.06L17.596 11h3.155c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-3.155l2.134 2.133c0.293 0.293 0.293 0.768 0 1.061-0.293 0.293-0.768 0.293-1.061 0L15.474 12.5H12.5v2.974l3.194 3.194c0.293 0.293 0.293 0.768 0 1.06-0.293 0.293-0.768 0.293-1.06 0L12.5 17.596v3.155c0 0.414-0.336 0.75-0.75 0.75S11 21.164 11 20.75v-3.155L8.867 19.73c-0.293 0.293-0.768 0.293-1.061 0-0.293-0.293-0.293-0.768 0-1.061L11 15.474V12.5H8.026l-3.194 3.194c-0.293 0.293-0.768 0.293-1.06 0-0.293-0.293-0.293-0.768 0-1.06L5.904 12.5H2.75C2.336 12.5 2 12.164 2 11.75S2.336 11 2.75 11h3.155L3.77 8.867c-0.292-0.293-0.292-0.768 0-1.061 0.293-0.293 0.768-0.293 1.061 0L8.026 11H11V8.026L7.806 4.832c-0.293-0.293-0.293-0.768 0-1.06 0.293-0.293 0.768-0.293 1.06 0L11 5.904V2.75C11 2.336 11.336 2 11.75 2z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12 2c0.414 0 0.75 0.336 0.75 0.75v1.5C12.75 4.664 12.414 5 12 5s-0.75-0.336-0.75-0.75v-1.5C11.25 2.336 11.586 2 12 2zm0 15c2.761 0 5-2.239 5-5s-2.239-5-5-5-5 2.239-5 5 2.239 5 5 5zm0-1.5c-1.933 0-3.5-1.567-3.5-3.5s1.567-3.5 3.5-3.5 3.5 1.567 3.5 3.5-1.567 3.5-3.5 3.5zm9.25-2.75c0.414 0 0.75-0.336 0.75-0.75s-0.336-0.75-0.75-0.75h-1.5C19.336 11.25 19 11.586 19 12s0.336 0.75 0.75 0.75h1.5zM12 19c0.414 0 0.75 0.336 0.75 0.75v1.5c0 0.414-0.336 0.75-0.75 0.75s-0.75-0.336-0.75-0.75v-1.5c0-0.414 0.336-0.75 0.75-0.75zm-7.75-6.25C4.664 12.75 5 12.414 5 12s-0.336-0.75-0.75-0.75h-1.5C2.336 11.25 2 11.586 2 12s0.336 0.75 0.75 0.75h1.5zM4.22 4.22c0.293-0.293 0.767-0.293 1.06 0l1.5 1.5c0.293 0.293 0.293 0.768 0 1.06-0.293 0.294-0.767 0.294-1.06 0l-1.5-1.5c-0.293-0.292-0.293-0.767 0-1.06zm1.06 15.56c-0.293 0.294-0.767 0.294-1.06 0-0.293-0.292-0.293-0.767 0-1.06l1.5-1.5c0.293-0.293 0.767-0.293 1.06 0 0.293 0.293 0.293 0.768 0 1.06l-1.5 1.5zm14.5-15.56c-0.293-0.293-0.767-0.293-1.06 0l-1.5 1.5c-0.293 0.293-0.293 0.768 0 1.06 0.293 0.294 0.767 0.294 1.06 0l1.5-1.5c0.293-0.292 0.293-0.767 0-1.06zm-1.06 15.56c0.293 0.294 0.767 0.294 1.06 0 0.293-0.292 0.293-0.767 0-1.06l-1.5-1.5c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.768 0 1.06l1.5 1.5z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M12.75 2.75C12.75 2.336 12.414 2 12 2s-0.75 0.336-0.75 0.75v1.5C11.25 4.664 11.586 5 12 5s0.75-0.336 0.75-0.75v-1.5zm6.28 2.22c0.293 0.293 0.293 0.767 0 1.06l-1.06 1.061c-0.293 0.293-0.768 0.293-1.061 0-0.293-0.293-0.293-0.768 0-1.06l1.06-1.061c0.294-0.293 0.768-0.293 1.061 0zM17.41 13c0.059-0.324 0.09-0.659 0.09-1 0-3.038-2.462-5.5-5.5-5.5S6.5 8.962 6.5 12c0 0.341 0.031 0.676 0.09 1H2.75C2.336 13 2 13.336 2 13.75s0.336 0.75 0.75 0.75h18.5c0.414 0 0.75-0.336 0.75-0.75S21.664 13 21.25 13h-3.84zM12 8c2.21 0 4 1.79 4 4 0 0.345-0.044 0.68-0.126 1H8.126C8.044 12.68 8 12.345 8 12c0-2.21 1.79-4 4-4zm-6 8.75C6 16.336 6.336 16 6.75 16h10.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75H6.75C6.336 17.5 6 17.164 6 16.75zm4 3c0-0.414 0.336-0.75 0.75-0.75h2.5c0.414 0 0.75 0.336 0.75 0.75s-0.336 0.75-0.75 0.75h-2.5c-0.414 0-0.75-0.336-0.75-0.75zM4.97 4.97c0.293-0.293 0.768-0.293 1.06 0l1.061 1.06c0.293 0.293 0.293 0.768 0 1.061-0.293 0.293-0.768 0.293-1.06 0L4.97 6.031c-0.293-0.294-0.293-0.768 0-1.061z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:pathData="M10.464 15.748L12.7 13.26c0.277-0.308 0.751-0.333 1.06-0.056 0.28 0.252 0.326 0.666 0.124 0.971l-0.068 0.088-1.111 1.236h2.276c0.594 0 0.938 0.648 0.645 1.134l-0.058 0.084-3.212 4.031c-0.258 0.324-0.73 0.378-1.054 0.12-0.294-0.235-0.365-0.646-0.182-0.963l0.063-0.091 2.242-2.815h-2.403c-0.613 0-0.953-0.685-0.623-1.168l0.065-0.083L12.7 13.26l-2.236 2.488zm2.538-10.74c3.168 0 4.966 2.098 5.227 4.631h0.08c2.04 0 3.692 1.649 3.692 3.683 0 2.033-1.653 3.682-3.692 3.682l-1.788 0.001c0.144-0.213 0.228-0.471 0.228-0.748 0-0.279-0.085-0.537-0.23-0.751h1.734c1.261 0 2.283-1 2.283-2.236 0-1.235-1.022-2.236-2.283-2.236h-0.69c-0.366 0-0.685-0.28-0.685-0.638 0-2.285-1.805-3.89-3.876-3.89-2.072 0-3.877 1.634-3.877 3.89 0 0.357-0.319 0.638-0.684 0.638H7.75c-1.262 0-2.284 1-2.284 2.236 0 1.235 1.022 2.237 2.283 2.237l1.762-0.001c-0.146 0.214-0.23 0.472-0.23 0.75s0.084 0.535 0.228 0.75l-1.816-0.002c-2.039 0-3.692-1.649-3.692-3.682 0-2.034 1.653-3.683 3.692-3.683h0.08c0.263-2.55 2.06-4.63 5.228-4.63zM10 2c1.617 0 3.05 0.815 3.9 2.062-0.29-0.035-0.59-0.053-0.898-0.053-0.395 0-0.775 0.029-1.139 0.085C11.335 3.72 10.69 3.5 10 3.5c-1.567 0-2.902 1.13-3.17 2.656L6.759 6.57c-0.084 0.478-0.5 0.827-0.985 0.827h-0.49C4.297 7.397 3.5 8.195 3.5 9.18c0 0.49 0.198 0.934 0.52 1.257-0.316 0.4-0.566 0.855-0.736 1.347C2.504 11.184 2 10.24 2 9.18 2 7.43 3.37 6 5.096 5.903l0.257-0.006C5.743 3.677 7.682 2 10 2z" android:fillColor="@color/fluent_default_icon_tint"/>
</vector>

View file

@ -1,40 +1,39 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.joinmastodon.android.ui.views.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:clipToPadding="false"> android:clipToPadding="false">
<FrameLayout <org.joinmastodon.android.ui.views.CheckableRelativeLayout
android:id="@+id/checkbox_wrap" android:id="@+id/checkbox_wrap"
android:layout_width="wrap_content" android:layout_width="56dp"
android:layout_height="match_parent"
android:paddingTop="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="46sp" android:layout_height="46sp"
android:duplicateParentState="true"> android:duplicateParentState="true">
<View <View
android:id="@+id/checkbox" android:id="@+id/checkbox"
android:layout_gravity="center_vertical"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_marginStart="-4dp" android:layout_gravity="center"
android:layout_marginTop="0dp"
android:layout_marginEnd="12dp"
android:duplicateParentState="true"/> android:duplicateParentState="true"/>
</FrameLayout> </FrameLayout>
</org.joinmastodon.android.ui.views.CheckableRelativeLayout>
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="-16dp" android:layout_marginHorizontal="-16dp"
android:layout_marginStart="-16dp"
android:layout_toEndOf="@id/checkbox_wrap"> android:layout_toEndOf="@id/checkbox_wrap">
<include layout="@layout/display_item_header" /> <include layout="@layout/display_item_header" />
</FrameLayout> </FrameLayout>
</org.joinmastodon.android.ui.views.CheckableRelativeLayout> </RelativeLayout>

View file

@ -13,7 +13,7 @@
<org.joinmastodon.android.ui.views.NestedRecyclerScrollView <org.joinmastodon.android.ui.views.NestedRecyclerScrollView
android:id="@+id/scroller" android:id="@+id/scroller"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:nestedScrollingEnabled="true"> android:nestedScrollingEnabled="true">
<org.joinmastodon.android.ui.views.CustomDrawingOrderLinearLayout <org.joinmastodon.android.ui.views.CustomDrawingOrderLinearLayout
@ -50,7 +50,7 @@
android:text="@string/follows_you" android:text="@string/follows_you"
android:textAllCaps="true" android:textAllCaps="true"
android:textColor="#fff" android:textColor="#fff"
android:textSize="14dp" android:textSize="14sp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
@ -80,11 +80,13 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/cover" android:layout_below="@id/cover"
android:layout_alignParentEnd="true"> android:layout_alignParentEnd="true"
android:clipChildren="false">
<FrameLayout <FrameLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="4dp"> android:layout_marginEnd="4dp">
@ -95,7 +97,8 @@
style="@style/Widget.Mastodon.M3.Button.Tonal" style="@style/Widget.Mastodon.M3.Button.Tonal"
android:background="@drawable/bg_button_m3_tonal_circle_selector" android:background="@drawable/bg_button_m3_tonal_circle_selector"
android:paddingStart="12dp" android:paddingStart="12dp"
android:drawableStart="@drawable/ic_fluent_alert_24_selector" /> android:drawableStart="@drawable/ic_fluent_alert_24_selector"
tools:ignore="RtlSymmetry" />
<ProgressBar <ProgressBar
android:id="@+id/notify_progress" android:id="@+id/notify_progress"
@ -112,6 +115,7 @@
<FrameLayout <FrameLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"> android:layout_marginEnd="16dp">
@ -219,7 +223,7 @@
android:layout_below="@id/username" android:layout_below="@id/username"
android:id="@+id/note_edit_wrap" android:id="@+id/note_edit_wrap"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="12dp"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:visibility="gone"> android:visibility="gone">
@ -227,32 +231,44 @@
android:id="@+id/note_edit" android:id="@+id/note_edit"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:minHeight="52dp"
android:inputType="textMultiLine|textCapSentences" android:paddingVertical="15dp"
android:textColor="?colorM3OnSurface"
android:inputType="text|textMultiLine|textCapSentences"
android:singleLine="false" android:singleLine="false"
android:drawablePadding="12dp"
android:drawableTint="?android:textColorSecondary"
android:background="@drawable/bg_note_edit" android:background="@drawable/bg_note_edit"
android:paddingEnd="48dp" android:paddingEnd="52dp"
android:paddingHorizontal="16dp" android:paddingStart="20dp"
android:elevation="0dp" android:elevation="0dp"
android:visibility="gone" android:hint="@string/mo_personal_note"
android:hint="@string/mo_personal_note"/> tools:ignore="RtlSymmetry" />
<FrameLayout
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="end">
<ImageButton <ImageButton
android:id="@+id/note_edit_confirm" android:id="@+id/note_save_btn"
android:layout_width="52dp"
android:layout_height="52dp"
android:visibility="invisible"
android:background="@drawable/bg_button_m3_text_circle"
android:tooltipText="@string/sk_confirm_changes"
android:contentDescription="@string/sk_confirm_changes"
android:src="@drawable/ic_fluent_checkmark_24_regular"/>
<ProgressBar
android:id="@+id/note_save_progress"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_gravity="center"
android:paddingHorizontal="6dp" style="?android:progressBarStyleSmall"
android:layout_marginBottom="7dp" android:indeterminate="true"
android:visibility="invisible" android:visibility="gone" />
android:layout_gravity="right|bottom"
android:background="@drawable/bg_button_m3_text_circle" </FrameLayout>
android:tooltipText="@string/mo_personal_note_confirm"
android:contentDescription="@string/mo_personal_note_confirm"
android:src="@drawable/ic_fluent_checkmark_24_regular"
/>
</FrameLayout> </FrameLayout>
<org.joinmastodon.android.ui.views.LinkedTextView <org.joinmastodon.android.ui.views.LinkedTextView
@ -287,19 +303,7 @@
android:hint="@string/display_name" android:hint="@string/display_name"
android:inputType="textPersonName|textCapWords" android:inputType="textPersonName|textCapWords"
android:padding="16dp" android:padding="16dp"
tools:text="Eugen" tools:text="Eugen" />
android:autofillHints="" />
<!-- <org.joinmastodon.android.ui.views.LinkedTextView-->
<!-- android:id="@+id/bio"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_below="@id/note_edit_wrap"-->
<!-- android:layout_marginLeft="16dp"-->
<!-- android:layout_marginRight="16dp"-->
<!-- android:textAppearance="@style/m3_body_large"-->
<!-- android:textSize="16sp"-->
<!-- tools:text="Founder, CEO and lead developer @Mastodon, Germany." />-->
</org.joinmastodon.android.ui.views.FloatingHintEditTextLayout> </org.joinmastodon.android.ui.views.FloatingHintEditTextLayout>
<org.joinmastodon.android.ui.views.FloatingHintEditTextLayout <org.joinmastodon.android.ui.views.FloatingHintEditTextLayout
@ -338,7 +342,8 @@
<me.grishka.appkit.views.UsableRecyclerView <me.grishka.appkit.views.UsableRecyclerView
android:id="@+id/metadata" android:id="@+id/metadata"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="4dp" /> android:paddingTop="4dp" />
<LinearLayout <LinearLayout

View file

@ -54,7 +54,7 @@
android:id="@+id/forward_report" android:id="@+id/forward_report"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="8dp"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:background="?android:selectableItemBackground"> android:background="?android:selectableItemBackground">

View file

@ -5,4 +5,5 @@
<item android:id="@+id/draft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_regular" /> <item android:id="@+id/draft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_regular" />
<item android:id="@+id/undraft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_filled" android:contentDescription="@string/sk_compose_no_draft" /> <item android:id="@+id/undraft" android:title="@string/sk_mark_as_draft" android:icon="@drawable/ic_fluent_drafts_24_filled" android:contentDescription="@string/sk_compose_no_draft" />
<item android:id="@+id/drafts" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular" /> <item android:id="@+id/drafts" android:title="@string/sk_unsent_posts" android:icon="@drawable/ic_fluent_folder_open_24_regular" />
<item android:id="@+id/preview" android:title="@string/sk_open_post_preview" android:icon="@drawable/ic_fluent_receipt_sparkles_24_regular" android:visible="false" />
</menu> </menu>

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/menu_group1"> <group android:id="@+id/menu_group1">
<item android:id="@+id/edit_note" android:title="@string/sk_add_note" android:icon="@drawable/ic_fluent_person_note_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/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/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/hide_boosts" android:title="@string/hide_boosts_from_user" android:icon="@drawable/ic_fluent_arrow_repeat_all_off_24_regular"/>
@ -9,7 +12,7 @@
<item android:id="@+id/report" android:title="@string/report_user" android:icon="@drawable/ic_fluent_warning_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"/> <item android:id="@+id/block_domain" android:title="@string/block_domain" android:icon="@drawable/ic_fluent_shield_prohibited_24_regular"/>
</group> </group>
<group android:id="@+id/menu_group2"> <group android:id="@+id/menu_group3">
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser" android:icon="@drawable/ic_fluent_globe_24_regular"/> <item android:id="@+id/open_in_browser" android:title="@string/open_in_browser" android:icon="@drawable/ic_fluent_globe_24_regular"/>
<item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/> <item android:id="@+id/share" android:title="@string/share_user" android:icon="@drawable/ic_fluent_share_24_regular"/>
<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"> <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">

View file

@ -416,4 +416,22 @@
<string name="sk_settings_lock_account">Neue Follower_innen manuell genehmigen</string> <string name="sk_settings_lock_account">Neue Follower_innen manuell genehmigen</string>
<string name="sk_timeline_cache_cleared">Start-Timeline geleert</string> <string name="sk_timeline_cache_cleared">Start-Timeline geleert</string>
<string name="sk_button_mutuals">Befreundet</string> <string name="sk_button_mutuals">Befreundet</string>
<string name="sk_icon_snowflake">Schneeflocke</string>
<string name="sk_icon_cloud">Wolke</string>
<string name="sk_icon_sunset">Sonnenuntergang</string>
<string name="sk_private_note_hint">Private Notiz über dieses Profil hinzufügen</string>
<string name="sk_icon_water">Wasser</string>
<string name="sk_icon_sun">Sonne</string>
<string name="sk_icon_rain">Regen</string>
<string name="sk_icon_thunderstorm">Gewitter</string>
<string name="sk_private_note_update_failed">Notiz speichern fehlgeschlagen</string>
<string name="sk_delete_note">Private Notiz löschen</string>
<string name="sk_confirm_changes">Änderungen bestätigen</string>
<string name="sk_settings_crash_log_unavailable">Keines verfügbar… noch</string>
<string name="sk_crash_log_copied">Absturzprotokoll kopiert</string>
<string name="sk_add_note">Private Notiz hinzufügen</string>
<string name="sk_settings_copy_crash_log">Neuestes Absturzprotokoll kopieren</string>
<string name="sk_open_post_preview">Vorschau öffnen</string>
<string name="sk_post_preview">Vorschau</string>
<string name="sk_private_note_confirm_delete">Private Notiz über %s löschen\?</string>
</resources> </resources>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View file

@ -403,15 +403,28 @@
<string name="sk_trending_links_info_banner">Estas noticias están dando que hablar en todo el Fediverso.</string> <string name="sk_trending_links_info_banner">Estas noticias están dando que hablar en todo el Fediverso.</string>
<string name="sk_blocked_accounts">Cuentas bloqueadas</string> <string name="sk_blocked_accounts">Cuentas bloqueadas</string>
<string name="sk_muted_accounts">Cuentas silenciadas</string> <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_settings_like_icon">Utilizar un corazón como icono de favorito</string>
<string name="sk_recently_used">Utilizado recientemente</string> <string name="sk_recently_used">Utilizado recientemente</string>
<string name="sk_set_as_default">Establecer por defecto</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_color_palette_default">Por defecto (%s)</string>
<string name="sk_settings_underlined_links">Enlaces subrayados</string> <string name="sk_settings_underlined_links">Subrayar enlaces</string>
<string name="sk_edit_alt_text">Editar el texto alternativo</string> <string name="sk_edit_alt_text">Editar el texto alternativo</string>
<string name="sk_settings_default_visibility">Visibilidad de la publicación predeterminada</string> <string name="sk_settings_default_visibility">Visibilidad de publicación predeterminada</string>
<string name="sk_settings_lock_account">Aprobar nuevos seguidores manualmente</string> <string name="sk_settings_lock_account">Aprobar nuevos seguidores manualmente</string>
<string name="sk_timeline_cache_cleared">Caché de la línea de tiempo de inicio borrada</string> <string name="sk_timeline_cache_cleared">Caché de la cronología de inicio borrada</string>
<string name="sk_settings_clear_timeline_cache">Borrar la caché de la línea de tiempo de inicio</string> <string name="sk_settings_clear_timeline_cache">Borrar la caché de la cronología de inicio</string>
<string name="sk_button_mutuals">Amigos</string> <string name="sk_button_mutuals">Amigos</string>
<string name="sk_icon_snowflake">Copos de nieve</string>
<string name="sk_private_note_confirm">Confirmar los cambios en la nota</string>
<string name="sk_private_note_update_failed">No se pudo guardar la nota</string>
<string name="sk_icon_cloud">Nube</string>
<string name="sk_delete_note">Borrar la nota personal</string>
<string name="sk_icon_sunset">Puesta de sol</string>
<string name="sk_private_note_hint">Añadir una nota personal sobre este perfil</string>
<string name="sk_icon_water">Agua</string>
<string name="sk_icon_sun">Sol</string>
<string name="sk_add_note">Añadir una nota personal</string>
<string name="sk_icon_rain">Lluvia</string>
<string name="sk_icon_thunderstorm">Tormenta eléctrica</string>
<string name="sk_private_note_confirm_delete">¿Borrar la nota personal sobre %s\?</string>
</resources> </resources>

View file

@ -403,4 +403,9 @@
<string name="sk_muted_accounts">حساب‌های خموش شده</string> <string name="sk_muted_accounts">حساب‌های خموش شده</string>
<string name="sk_recently_used">اخیرا مورد استفاده قرار گرفته</string> <string name="sk_recently_used">اخیرا مورد استفاده قرار گرفته</string>
<string name="sk_edit_alt_text">ویرایش متن جایگزین</string> <string name="sk_edit_alt_text">ویرایش متن جایگزین</string>
<string name="sk_settings_default_visibility">نمایانی فرسته پیش‌گزیده</string>
<string name="sk_settings_lock_account">تایید دستی پیگیران جدید</string>
<string name="sk_timeline_cache_cleared">کش خط زمانی خانه پاک شد</string>
<string name="sk_button_mutuals">متقابل</string>
<string name="sk_settings_clear_timeline_cache">پاک کردن کش خط زمانی خانه</string>
</resources> </resources>

View file

@ -297,7 +297,7 @@
<string name="sk_settings_allow_remote_loading">Charger des informations à partir d\'instances distantes</string> <string name="sk_settings_allow_remote_loading">Charger des informations à partir d\'instances distantes</string>
<string name="sk_no_remote_info_hint">informations distantes indisponibles</string> <string name="sk_no_remote_info_hint">informations distantes indisponibles</string>
<string name="sk_error_loading_profile">Échec du chargement du profil via %s</string> <string name="sk_error_loading_profile">Échec du chargement du profil via %s</string>
<string name="sk_settings_allow_remote_loading_explanation">Essayez de récupérer des listes plus précises pour les abonnés, les likes et les boosts en chargeant les informations à partir de l\'instance d\'origine.</string> <string name="sk_settings_allow_remote_loading_explanation">Essaye de récupérer des listes plus précises pour les abonné·e·s, les favoris et les boosts en chargeant les informations à partir de l\'instance d\'origine.</string>
<string name="sk_settings_auto_reveal_equal_spoilers">Révéler les CW identiques dans les réponses</string> <string name="sk_settings_auto_reveal_equal_spoilers">Révéler les CW identiques dans les réponses</string>
<string name="sk_settings_auto_reveal_nobody">Jamais</string> <string name="sk_settings_auto_reveal_nobody">Jamais</string>
<string name="sk_settings_auto_reveal_author">Réponses du même auteur</string> <string name="sk_settings_auto_reveal_author">Réponses du même auteur</string>
@ -412,7 +412,7 @@
<string name="sk_message_cache_cleared">Cache des messages vidé</string> <string name="sk_message_cache_cleared">Cache des messages vidé</string>
<string name="sk_settings_clear_timeline_cache">Effacer le cache du fil d\'accueil</string> <string name="sk_settings_clear_timeline_cache">Effacer le cache du fil d\'accueil</string>
<string name="sk_settings_default_visibility">Visibilité de publication par défaut</string> <string name="sk_settings_default_visibility">Visibilité de publication par défaut</string>
<string name="sk_settings_lock_account">Approuver manuellement les nouveaux abonnés</string> <string name="sk_settings_lock_account">Approuver manuellement les nouveaux·elles abonné·e·s</string>
<string name="sk_timeline_cache_cleared">Cache du fil d\'accueil vidé</string> <string name="sk_timeline_cache_cleared">Cache du fil d\'accueil vidé</string>
<string name="sk_button_mutuals">Amis</string> <string name="sk_button_mutuals">Suivi mutuel</string>
</resources> </resources>

View file

@ -157,7 +157,7 @@
<string name="sk_visibility_unlisted">Nenavedeno</string> <string name="sk_visibility_unlisted">Nenavedeno</string>
<string name="sk_lists_with_user">Liste s %s</string> <string name="sk_lists_with_user">Liste s %s</string>
<string name="sk_settings_show_federated_timeline">Prikaži objedinjenu vremensku traku</string> <string name="sk_settings_show_federated_timeline">Prikaži objedinjenu vremensku traku</string>
<string name="sk_translated_using">"Prevedeno uporabom %s"</string> <string name="sk_translated_using">Prevedeno uporabom %s</string>
<string name="sk_settings_translate_only_opened">Prevodi samo otvorene objave</string> <string name="sk_settings_translate_only_opened">Prevodi samo otvorene objave</string>
<string name="sk_loading_fediverse_resource_title">Pretraga na Fediversu</string> <string name="sk_loading_fediverse_resource_title">Pretraga na Fediversu</string>
<string name="sk_loading_resource_on_instance_title">Pretraga na %s</string> <string name="sk_loading_resource_on_instance_title">Pretraga na %s</string>

View file

@ -4,7 +4,7 @@
<string name="sk_delete_and_redraft">削除して再編集</string> <string name="sk_delete_and_redraft">削除して再編集</string>
<string name="sk_confirm_delete_and_redraft_title">投稿を削除して再編集</string> <string name="sk_confirm_delete_and_redraft_title">投稿を削除して再編集</string>
<string name="sk_confirm_delete_and_redraft">本当にこの投稿を削除して再編集しますか?</string> <string name="sk_confirm_delete_and_redraft">本当にこの投稿を削除して再編集しますか?</string>
<string name="sk_pin_post">プロファイルに固定</string> <string name="sk_pin_post">プロフィールに固定</string>
<string name="sk_confirm_pin_post_title">投稿をプロフィールに固定</string> <string name="sk_confirm_pin_post_title">投稿をプロフィールに固定</string>
<string name="sk_confirm_pin_post">この投稿をあなたのプロフィールに固定しますか?</string> <string name="sk_confirm_pin_post">この投稿をあなたのプロフィールに固定しますか?</string>
<string name="sk_pinning">投稿を固定しています…</string> <string name="sk_pinning">投稿を固定しています…</string>
@ -154,7 +154,7 @@
<string name="sk_scheduled_too_soon">投稿は10分以上後の予約である必要があります。</string> <string name="sk_scheduled_too_soon">投稿は10分以上後の予約である必要があります。</string>
<string name="sk_settings_unifiedpush">UnifiedPush を使用</string> <string name="sk_settings_unifiedpush">UnifiedPush を使用</string>
<string name="sk_settings_unifiedpush_choose">ディストリビューターを選択</string> <string name="sk_settings_unifiedpush_choose">ディストリビューターを選択</string>
<string name="sk_list_replies_policy_followed">フォローされたユーザー</string> <string name="sk_list_replies_policy_followed">フォロー中のユーザー</string>
<string name="sk_delete_list">リストを削除</string> <string name="sk_delete_list">リストを削除</string>
<string name="sk_delete_list_confirm">本当にリスト “%s” を削除しますか?</string> <string name="sk_delete_list_confirm">本当にリスト “%s” を削除しますか?</string>
<string name="sk_edit_list_title">リストを編集</string> <string name="sk_edit_list_title">リストを編集</string>
@ -288,7 +288,7 @@
<string name="sk_settings_display_pronouns_in_user_listings">ユーザーリストに代名詞を表示</string> <string name="sk_settings_display_pronouns_in_user_listings">ユーザーリストに代名詞を表示</string>
<string name="sk_settings_unifiedpush_no_distributor_body">UnifiedPush による通知を動作させるにはディストリビューターをインストールする必要があります。詳しくは https://unifiedpush.org/ をご覧ください</string> <string name="sk_settings_unifiedpush_no_distributor_body">UnifiedPush による通知を動作させるにはディストリビューターをインストールする必要があります。詳しくは https://unifiedpush.org/ をご覧ください</string>
<string name="sk_list_replies_policy_list">リストのメンバー</string> <string name="sk_list_replies_policy_list">リストのメンバー</string>
<string name="sk_settings_support_local_only">サーバーはローカルのみの投稿をサポートしていま</string> <string name="sk_settings_support_local_only">ローカルのみの投稿をサポートするサーバー</string>
<string name="sk_spoiler_show">コンテンツを表示</string> <string name="sk_spoiler_show">コンテンツを表示</string>
<string name="sk_advanced_options_hide">高度な設定を非表示にする</string> <string name="sk_advanced_options_hide">高度な設定を非表示にする</string>
<string name="sk_signed_up">登録済み</string> <string name="sk_signed_up">登録済み</string>
@ -302,7 +302,7 @@
</plurals> </plurals>
<string name="sk_reported">報告済み</string> <string name="sk_reported">報告済み</string>
<string name="sk_set_as_default">デフォルトに設定</string> <string name="sk_set_as_default">デフォルトに設定</string>
<string name="sk_settings_like_icon">お気に入りアイコンにハートを使用する</string> <string name="sk_settings_like_icon">お気に入りアイコンにハートを使用</string>
<string name="sk_content_type_html">HTML</string> <string name="sk_content_type_html">HTML</string>
<string name="sk_settings_color_palette_default">デフォルト (%s)</string> <string name="sk_settings_color_palette_default">デフォルト (%s)</string>
<string name="sk_open_in_app">アプリで開く</string> <string name="sk_open_in_app">アプリで開く</string>
@ -354,4 +354,34 @@
<string name="sk_show_thread">スレッドを表示</string> <string name="sk_show_thread">スレッドを表示</string>
<string name="sk_settings_clear_timeline_cache">ホームタイムラインキャッシュをクリア</string> <string name="sk_settings_clear_timeline_cache">ホームタイムラインキャッシュをクリア</string>
<string name="sk_add_timeline_tag_error_empty">ハッシュタグは空欄にできません</string> <string name="sk_add_timeline_tag_error_empty">ハッシュタグは空欄にできません</string>
<string name="sk_settings_allow_remote_loading_explanation">発信元のインスタンスから情報を読み込んで、フォロワー、いいね、ブーストのより正確なリストを取得しましょう。</string>
<string name="sk_disable_pill_shaped_active_indicator">錠剤型のアクティブタブインジケーターを無効にする</string>
<string name="sk_settings_show_emoji_reactions_only_opened">投稿を開いた時のみ</string>
<string name="sk_settings_glitch_instance">Glitchのローカル限定モード</string>
<string name="sk_settings_allow_remote_loading">リモートインスタンスから情報を読み込む</string>
<string name="sk_notify_posts_info_banner">投稿通知を有効にすると、その人の新しい投稿がここに表示されます。</string>
<string name="sk_settings_auto_reveal_anyone">全員の返信</string>
<string name="sk_settings_show_emoji_reactions_hide_empty">空の絵文字リアクションを隠す</string>
<string name="sk_settings_show_emoji_reactions">絵文字リアクションをタイムラインに表示</string>
<string name="sk_settings_default_visibility">投稿のデフォルト公開範囲</string>
<string name="sk_settings_underlined_links">下線付きリンク</string>
<string name="sk_list_replies_policy_none">なし</string>
<string name="sk_settings_show_alt_indicator">代替テキストがあることを示す表示</string>
<string name="sk_settings_auto_reveal_nobody">なし</string>
<string name="sk_settings_lock_account">新しいフォロワーを手動で承認</string>
<string name="sk_settings_glitch_mode_explanation">ホームインスタンスがGlitchで動作している場合、これを有効にしてください。HometownやAkkomaでは不要です。</string>
<string name="sk_settings_auto_reveal_equal_spoilers">返信内にある同一のCWを自動で展開</string>
<string name="sk_settings_show_no_alt_indicator">代替テキストがないことを示す表示</string>
<string name="sk_no_remote_info_hint">リモートの情報が利用できません</string>
<string name="sk_settings_auto_reveal_author">同じ作成者の返信</string>
<string name="sk_error_loading_profile">%s からのプロフィールの読み込みに失敗しました</string>
<string name="sk_trending_posts_info_banner">これらの投稿は現在Fediverseで人気を集めています。</string>
<string name="sk_in_reply">返信</string>
<string name="sk_settings_confirm_before_reblog">ブーストする前に確認</string>
<string name="sk_settings_emoji_reactions_explanation">投稿に対する絵文字リアクションを表示、追加することができます。様々なFediverseサーバーがこれに対応していますが、Mastodonは対応していません。</string>
<string name="sk_recently_used">最近使用</string>
<string name="sk_button_mutuals">相互フォロー中</string>
<string name="sk_notify_poll_results">投票結果</string>
<string name="sk_post_contains_media">メディアを含む投稿</string>
<string name="sk_edit_alt_text">代替テキストを編集</string>
</resources> </resources>

View file

@ -409,4 +409,5 @@
<string name="sk_settings_lock_account">Aprobați manual urmăritori noi</string> <string name="sk_settings_lock_account">Aprobați manual urmăritori noi</string>
<string name="sk_timeline_cache_cleared">Memoria cache a cronologiei acasă a fost ștearsă</string> <string name="sk_timeline_cache_cleared">Memoria cache a cronologiei acasă a fost ștearsă</string>
<string name="sk_settings_clear_timeline_cache">Ștergeți memoria cache a cronologiei acasă</string> <string name="sk_settings_clear_timeline_cache">Ștergeți memoria cache a cronologiei acasă</string>
<string name="sk_button_mutuals">Prieteni</string>
</resources> </resources>

View file

@ -415,4 +415,24 @@
<string name="sk_settings_default_visibility">Усталена видимість дописів</string> <string name="sk_settings_default_visibility">Усталена видимість дописів</string>
<string name="sk_settings_lock_account">Затверджувати нових підписників уручну</string> <string name="sk_settings_lock_account">Затверджувати нових підписників уручну</string>
<string name="sk_timeline_cache_cleared">Кеш домашньої стрічки очищено</string> <string name="sk_timeline_cache_cleared">Кеш домашньої стрічки очищено</string>
<string name="sk_button_mutuals">Друзі</string>
<string name="sk_icon_snowflake">Сніжинка</string>
<string name="sk_private_note_confirm">Підтвердити зміни</string>
<string name="sk_private_note_update_failed">Не вдалося зберегти нотатку</string>
<string name="sk_icon_cloud">Хмара</string>
<string name="sk_delete_note">Видалити нотатку</string>
<string name="sk_icon_sunset">Захід сонця</string>
<string name="sk_private_note_hint">Додати нотатку для себе про цей профіль</string>
<string name="sk_icon_water">Вода</string>
<string name="sk_icon_sun">Сонце</string>
<string name="sk_add_note">Додати нотатку</string>
<string name="sk_icon_rain">Дощ</string>
<string name="sk_icon_thunderstorm">Гроза</string>
<string name="sk_private_note_confirm_delete">Видалити нотатку про %s\?</string>
<string name="sk_confirm_changes">Підтвердити зміни</string>
<string name="sk_settings_crash_log_unavailable">Немає… поки що</string>
<string name="sk_crash_log_copied">Журнал збоїв скопійовано</string>
<string name="sk_settings_copy_crash_log">Копіювати останній журнал збоїв</string>
<string name="sk_open_post_preview">Попередній перегляд допису</string>
<string name="sk_post_preview">Попередній перегляд</string>
</resources> </resources>

View file

@ -2,29 +2,29 @@
<resources> <resources>
<string name="sk_pinned_posts">置顶</string> <string name="sk_pinned_posts">置顶</string>
<string name="sk_delete_and_redraft">删除并重新编辑</string> <string name="sk_delete_and_redraft">删除并重新编辑</string>
<string name="sk_confirm_delete_and_redraft_title">删除并重新编辑</string> <string name="sk_confirm_delete_and_redraft_title">删除并重新编辑</string>
<string name="sk_confirm_delete_and_redraft">确定要删除并重新编辑此文吗?</string> <string name="sk_confirm_delete_and_redraft">确定要删除并重新编辑此文吗?</string>
<string name="sk_pin_post">置顶</string> <string name="sk_pin_post">置顶</string>
<string name="sk_confirm_pin_post_title">置顶</string> <string name="sk_confirm_pin_post_title">置顶</string>
<string name="sk_confirm_pin_post">你确定要在资料页置顶此文吗?</string> <string name="sk_confirm_pin_post">你确定要在资料页置顶此文吗?</string>
<string name="sk_pinning">正在置顶文…</string> <string name="sk_pinning">正在置顶文…</string>
<string name="sk_unpin_post">取消置顶</string> <string name="sk_unpin_post">取消置顶</string>
<string name="sk_confirm_unpin_post_title">取消文置顶</string> <string name="sk_confirm_unpin_post_title">取消文置顶</string>
<string name="sk_confirm_unpin_post">你确定不再置顶此文吗?</string> <string name="sk_confirm_unpin_post">你确定不再置顶此文吗?</string>
<string name="sk_unpinning">正在取消置顶…</string> <string name="sk_unpinning">正在取消置顶…</string>
<string name="sk_image_description">图片描述</string> <string name="sk_image_description">图片描述</string>
<string name="sk_visibility_unlisted">不公开</string> <string name="sk_visibility_unlisted">不公开</string>
<string name="sk_federated_timeline">联邦时间轴</string> <string name="sk_federated_timeline">联邦时间轴</string>
<string name="sk_federated_timeline_info_banner">这些是互联实例中最新发布的文。</string> <string name="sk_federated_timeline_info_banner">这些是互联实例中最新发布的文。</string>
<string name="sk_app_name">Megalodon</string> <string name="sk_app_name">Megalodon</string>
<string name="sk_settings_show_replies">显示回复</string> <string name="sk_settings_show_replies">显示回复</string>
<string name="sk_settings_show_boosts">显示转嘟</string> <string name="sk_settings_show_boosts">显示转嘟</string>
<string name="sk_settings_load_new_posts">自动加载新</string> <string name="sk_settings_load_new_posts">自动加载新</string>
<string name="sk_settings_show_interaction_counts">显示互动次数</string> <string name="sk_settings_show_interaction_counts">显示互动次数</string>
<string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string> <string name="sk_settings_app_version">Megalodon v%1$s (%2$d)</string>
<string name="sk_mark_media_as_sensitive">标记为敏感媒体</string> <string name="sk_mark_media_as_sensitive">标记为敏感媒体</string>
<string name="sk_user_post_notifications_on">启用 %s 的文通知</string> <string name="sk_user_post_notifications_on">启用 %s 的文通知</string>
<string name="sk_user_post_notifications_off">关闭 %s 的文通知</string> <string name="sk_user_post_notifications_off">关闭 %s 的文通知</string>
<string name="sk_update_available">Megalodon %s 已经可以下载了。</string> <string name="sk_update_available">Megalodon %s 已经可以下载了。</string>
<string name="sk_update_ready">Megalodon %s 已下载,准备安装。</string> <string name="sk_update_ready">Megalodon %s 已下载,准备安装。</string>
<string name="sk_check_for_update">检查更新</string> <string name="sk_check_for_update">检查更新</string>
@ -45,8 +45,8 @@
<string name="sk_color_palette_blue"></string> <string name="sk_color_palette_blue"></string>
<string name="sk_color_palette_brown"></string> <string name="sk_color_palette_brown"></string>
<string name="sk_color_palette_yellow"></string> <string name="sk_color_palette_yellow"></string>
<string name="sk_notification_type_status"></string> <string name="sk_notification_type_status"></string>
<string name="sk_notification_type_posts">文通知</string> <string name="sk_notification_type_posts">文通知</string>
<string name="sk_translate_post">翻译</string> <string name="sk_translate_post">翻译</string>
<string name="sk_translate_show_original">显示原文</string> <string name="sk_translate_show_original">显示原文</string>
<string name="sk_poll_allow_multiple">允许多选</string> <string name="sk_poll_allow_multiple">允许多选</string>
@ -84,8 +84,8 @@
<string name="sk_loading_fediverse_resource_title">在联邦宇宙上查找</string> <string name="sk_loading_fediverse_resource_title">在联邦宇宙上查找</string>
<string name="sk_undo_reblog">撤销转嘟</string> <string name="sk_undo_reblog">撤销转嘟</string>
<string name="sk_reblog_with_visibility">转嘟可见性</string> <string name="sk_reblog_with_visibility">转嘟可见性</string>
<string name="sk_quote_post">引用此</string> <string name="sk_quote_post">引用此</string>
<string name="sk_copy_link_to_post">复制文链接</string> <string name="sk_copy_link_to_post">复制文链接</string>
<string name="sk_hashtags_you_follow">你关注的标签</string> <string name="sk_hashtags_you_follow">你关注的标签</string>
<string name="sk_loading_resource_on_instance_title">在 %s 上查找</string> <string name="sk_loading_resource_on_instance_title">在 %s 上查找</string>
<string name="sk_resource_not_found">找不到资源</string> <string name="sk_resource_not_found">找不到资源</string>
@ -101,27 +101,27 @@
<string name="sk_already_reblogged">已转嘟过</string> <string name="sk_already_reblogged">已转嘟过</string>
<string name="sk_reply_as">用其他账号回复</string> <string name="sk_reply_as">用其他账号回复</string>
<string name="sk_settings_uniform_icon_for_notifications">所有通知的统一图标</string> <string name="sk_settings_uniform_icon_for_notifications">所有通知的统一图标</string>
<string name="sk_unsent_posts">未发送的</string> <string name="sk_unsent_posts">未发送的</string>
<string name="sk_confirm_delete_draft_title">删除草稿</string> <string name="sk_confirm_delete_draft_title">删除草稿</string>
<string name="sk_draft">草稿</string> <string name="sk_draft">草稿</string>
<string name="sk_schedule">定时</string> <string name="sk_schedule">定时</string>
<string name="sk_confirm_delete_scheduled_post_title">删除定时</string> <string name="sk_confirm_delete_scheduled_post_title">删除定时</string>
<string name="sk_confirm_delete_scheduled_post">你确定要删除此定时文吗?</string> <string name="sk_confirm_delete_scheduled_post">你确定要删除此定时文吗?</string>
<string name="sk_draft_or_schedule">草稿或定时</string> <string name="sk_draft_or_schedule">草稿或定时</string>
<string name="sk_compose_draft">文将保存为草稿。</string> <string name="sk_compose_draft">文将保存为草稿。</string>
<string name="sk_compose_scheduled">定时于</string> <string name="sk_compose_scheduled">定时于</string>
<string name="sk_draft_saved">草稿已保存</string> <string name="sk_draft_saved">草稿已保存</string>
<string name="sk_post_scheduled">文已定时</string> <string name="sk_post_scheduled">文已定时</string>
<string name="sk_forward_report_to">转嘟给 %s</string> <string name="sk_forward_report_to">转嘟给 %s</string>
<string name="sk_confirm_delete_draft">你确定要删除此文草稿吗?</string> <string name="sk_confirm_delete_draft">你确定要删除此文草稿吗?</string>
<string name="sk_scheduled_too_soon_title">定时时间过早</string> <string name="sk_scheduled_too_soon_title">定时时间过早</string>
<string name="sk_scheduled_too_soon">文只能设置为 10 分钟或更晚发送。</string> <string name="sk_scheduled_too_soon">文只能设置为 10 分钟或更晚发送。</string>
<string name="sk_save_draft">保存为草稿?</string> <string name="sk_save_draft">保存为草稿?</string>
<string name="sk_save_changes">保存更改?</string> <string name="sk_save_changes">保存更改?</string>
<string name="sk_confirm_save_draft">保存草稿?</string> <string name="sk_confirm_save_draft">保存草稿?</string>
<string name="sk_confirm_save_changes">保存更改?</string> <string name="sk_confirm_save_changes">保存更改?</string>
<string name="sk_mark_as_draft">标记为草稿</string> <string name="sk_mark_as_draft">标记为草稿</string>
<string name="sk_schedule_post">定时</string> <string name="sk_schedule_post">定时</string>
<string name="sk_compose_no_schedule">不要定时</string> <string name="sk_compose_no_schedule">不要定时</string>
<string name="sk_compose_no_draft">不要标记为草稿</string> <string name="sk_compose_no_draft">不要标记为草稿</string>
<string name="sk_settings_reduce_motion">减少动画效果</string> <string name="sk_settings_reduce_motion">减少动画效果</string>
@ -151,14 +151,14 @@
<string name="sk_timeline_local">本站</string> <string name="sk_timeline_local">本站</string>
<string name="sk_alt_text_missing">至少有一个附件不包含描述。</string> <string name="sk_alt_text_missing">至少有一个附件不包含描述。</string>
<string name="sk_publish_anyway">仍然发布</string> <string name="sk_publish_anyway">仍然发布</string>
<string name="sk_notify_posts_info_banner">如果你为某些人启用了帖文通知,其新帖文将显示在此处。</string> <string name="sk_notify_posts_info_banner">如果你为某些人启用了嘟文通知,其新嘟文将显示在此处。</string>
<string name="sk_timelines">时间线</string> <string name="sk_timelines">时间线</string>
<string name="sk_edit_timelines">编辑时间线</string> <string name="sk_edit_timelines">编辑时间线</string>
<string name="sk_alt_button">ALT</string> <string name="sk_alt_button">ALT</string>
<string name="sk_post_edited">编辑</string> <string name="sk_post_edited">编辑</string>
<string name="sk_notification_type_update">编辑</string> <string name="sk_notification_type_update">编辑</string>
<string name="sk_alt_text_missing_title">缺少 ALT 文本</string> <string name="sk_alt_text_missing_title">缺少 ALT 文本</string>
<string name="sk_timeline_posts"></string> <string name="sk_timeline_posts"></string>
<string name="sk_timelines_add">添加</string> <string name="sk_timelines_add">添加</string>
<string name="sk_timeline">时间线</string> <string name="sk_timeline">时间线</string>
<string name="sk_list">列表</string> <string name="sk_list">列表</string>
@ -223,7 +223,7 @@
<string name="sk_icon_headphones">耳机</string> <string name="sk_icon_headphones">耳机</string>
<string name="sk_icon_human">人类</string> <string name="sk_icon_human">人类</string>
<string name="sk_icon_globe">地球</string> <string name="sk_icon_globe">地球</string>
<string name="sk_notify_update">编辑已转嘟</string> <string name="sk_notify_update">编辑已转嘟</string>
<string name="sk_icon_pin">钉子</string> <string name="sk_icon_pin">钉子</string>
<string name="sk_remove_follower_confirm">通过屏蔽并立即解除屏蔽以移除%s的关注者身份</string> <string name="sk_remove_follower_confirm">通过屏蔽并立即解除屏蔽以移除%s的关注者身份</string>
<string name="sk_icon_clapper_board">拍板</string> <string name="sk_icon_clapper_board">拍板</string>
@ -248,7 +248,7 @@
<string name="sk_settings_glitch_mode_explanation">如果你的主实例运行 Glitch请启用此功能。Hometown 或 Akkoma 不需要启用。</string> <string name="sk_settings_glitch_mode_explanation">如果你的主实例运行 Glitch请启用此功能。Hometown 或 Akkoma 不需要启用。</string>
<string name="sk_sign_ups">用户注册</string> <string name="sk_sign_ups">用户注册</string>
<string name="sk_new_reports">新举报</string> <string name="sk_new_reports">新举报</string>
<string name="sk_settings_see_new_posts_button">“查看新文” 按钮</string> <string name="sk_settings_see_new_posts_button">“查看新文” 按钮</string>
<string name="sk_settings_server_version">服务器版本: %s</string> <string name="sk_settings_server_version">服务器版本: %s</string>
<string name="sk_notify_poll_results">投票结果</string> <string name="sk_notify_poll_results">投票结果</string>
<string name="sk_expand">展开</string> <string name="sk_expand">展开</string>
@ -256,8 +256,8 @@
<string name="sk_unfinished_attachments">正在上传附件</string> <string name="sk_unfinished_attachments">正在上传附件</string>
<string name="sk_unfinished_attachments_message">部分附件尚未上传完毕。</string> <string name="sk_unfinished_attachments_message">部分附件尚未上传完毕。</string>
<string name="sk_filtered">已过滤:%s</string> <string name="sk_filtered">已过滤:%s</string>
<string name="sk_settings_collapse_long_posts">折叠很长的</string> <string name="sk_settings_collapse_long_posts">折叠很长的</string>
<string name="sk_settings_prefix_reply_cw_with_re">回复时在 CW 前加上 “re:”</string> <string name="sk_settings_prefix_reply_cw_with_re">回复时在内容警告信息前加上 “re:”</string>
<string name="sk_spectator_mode">旁观模式</string> <string name="sk_spectator_mode">旁观模式</string>
<string name="sk_settings_hide_interaction">隐藏互动按钮</string> <string name="sk_settings_hide_interaction">隐藏互动按钮</string>
<string name="sk_settings_reply_visibility_self">对我的回复</string> <string name="sk_settings_reply_visibility_self">对我的回复</string>
@ -287,7 +287,7 @@
<string name="sk_content_type_markdown">Markdown</string> <string name="sk_content_type_markdown">Markdown</string>
<string name="sk_content_type_bbcode">BBCode</string> <string name="sk_content_type_bbcode">BBCode</string>
<string name="sk_content_type_mfm">MFM</string> <string name="sk_content_type_mfm">MFM</string>
<string name="sk_settings_content_types">启用文格式</string> <string name="sk_settings_content_types">启用文格式</string>
<string name="sk_settings_content_types_explanation">允许在创建文章时设置类似Markdown的内容类型。注意不是所有的实例都支持这个。</string> <string name="sk_settings_content_types_explanation">允许在创建文章时设置类似Markdown的内容类型。注意不是所有的实例都支持这个。</string>
<string name="sk_settings_default_content_type">默认的内容类型</string> <string name="sk_settings_default_content_type">默认的内容类型</string>
<string name="sk_timeline_bubble">Bubble</string> <string name="sk_timeline_bubble">Bubble</string>
@ -307,7 +307,7 @@
<string name="sk_edit_timeline_tag_all">…并包含其中全部</string> <string name="sk_edit_timeline_tag_all">…并包含其中全部</string>
<string name="sk_edit_timeline_tags_explanation">请注意,服务器会处理这些操作。可能不支持合并这些操作。</string> <string name="sk_edit_timeline_tags_explanation">请注意,服务器会处理这些操作。可能不支持合并这些操作。</string>
<string name="sk_settings_allow_remote_loading_explanation">尝试从原实例加载信息,以获取更准确的关注者、点赞和转发列表。</string> <string name="sk_settings_allow_remote_loading_explanation">尝试从原实例加载信息,以获取更准确的关注者、点赞和转发列表。</string>
<string name="sk_settings_auto_reveal_equal_spoilers">在回复中自动显示相同的 CWs</string> <string name="sk_settings_auto_reveal_equal_spoilers">在回复中自动显示相同的内容警告</string>
<string name="sk_settings_forward_report_default">\"转发报告 \"开关默认值</string> <string name="sk_settings_forward_report_default">\"转发报告 \"开关默认值</string>
<string name="sk_list_exclusive_switch_explanation">排除列表的成员不会显示在你的主页时间线上--如果你的实例支持的话。</string> <string name="sk_list_exclusive_switch_explanation">排除列表的成员不会显示在你的主页时间线上--如果你的实例支持的话。</string>
<string name="sk_advanced_options_show">显示高级选项</string> <string name="sk_advanced_options_show">显示高级选项</string>
@ -319,7 +319,7 @@
<string name="sk_disable_pill_shaped_active_indicator">禁用药丸状的活跃选项卡指示器</string> <string name="sk_disable_pill_shaped_active_indicator">禁用药丸状的活跃选项卡指示器</string>
<string name="sk_settings_true_black">全黑模式</string> <string name="sk_settings_true_black">全黑模式</string>
<string name="sk_settings_display_pronouns_in_timelines">在时间线上显示性别代词</string> <string name="sk_settings_display_pronouns_in_timelines">在时间线上显示性别代词</string>
<string name="sk_settings_emoji_reactions_explanation">显示对文的表情回应,并允许你添加自己的表情回应。许多 Fediverse 服务器支持此功能,但 Mastodon 不支持。</string> <string name="sk_settings_emoji_reactions_explanation">显示对文的表情回应,并允许你添加自己的表情回应。许多 Fediverse 服务器支持此功能,但 Mastodon 不支持。</string>
<string name="sk_settings_emoji_reactions_in_lists">在时间线中显示表情回应</string> <string name="sk_settings_emoji_reactions_in_lists">在时间线中显示表情回应</string>
<string name="sk_settings_emoji_reactions_in_lists_explanation">是否在时间线中显示表情回应。如果此选项为关闭,则只有在查看对话时才会显示表情回应。</string> <string name="sk_settings_emoji_reactions_in_lists_explanation">是否在时间线中显示表情回应。如果此选项为关闭,则只有在查看对话时才会显示表情回应。</string>
<string name="sk_button_react">用表情回应</string> <string name="sk_button_react">用表情回应</string>
@ -349,12 +349,12 @@
<string name="sk_icon_doctor">医生</string> <string name="sk_icon_doctor">医生</string>
<string name="sk_icon_diamond">钻石</string> <string name="sk_icon_diamond">钻石</string>
<string name="sk_icon_umbrella">雨伞</string> <string name="sk_icon_umbrella">雨伞</string>
<string name="sk_edit_timeline_tag_main">包含标签的文…</string> <string name="sk_edit_timeline_tag_main">包含标签的文…</string>
<string name="sk_edit_timeline_tag_none">…但都不包含</string> <string name="sk_edit_timeline_tag_none">…但都不包含</string>
<string name="sk_edit_timeline_tag_any">…或包含其中任何一个</string> <string name="sk_edit_timeline_tag_any">…或包含其中任何一个</string>
<string name="sk_edit_timeline_tag_hint">输入标签…</string> <string name="sk_edit_timeline_tag_hint">输入标签…</string>
<string name="sk_edit_timeline_tags_hint">输入标签…</string> <string name="sk_edit_timeline_tags_hint">输入标签…</string>
<string name="sk_hashtag_timeline_local_only_switch">仅显示本地文?</string> <string name="sk_hashtag_timeline_local_only_switch">仅显示本地文?</string>
<string name="sk_add_timeline_tag_error_empty">标签不可为空</string> <string name="sk_add_timeline_tag_error_empty">标签不可为空</string>
<string name="sk_gif_badge">GIF</string> <string name="sk_gif_badge">GIF</string>
<string name="sk_settings_prefix_replies_always">回复至任何人</string> <string name="sk_settings_prefix_replies_always">回复至任何人</string>
@ -370,29 +370,29 @@
<string name="sk_tab_profile">个人资料</string> <string name="sk_tab_profile">个人资料</string>
<string name="sk_settings_show_labels_in_navigation_bar">在导航栏中显示选项卡标签</string> <string name="sk_settings_show_labels_in_navigation_bar">在导航栏中显示选项卡标签</string>
<string name="sk_reacted_with">%1$s 回应了 %2$s</string> <string name="sk_reacted_with">%1$s 回应了 %2$s</string>
<string name="sk_bubble_timeline_info_banner">这些都是实例管理员从网络中最新精选出来的文。</string> <string name="sk_bubble_timeline_info_banner">这些都是实例管理员从网络中最新精选出来的文。</string>
<string name="sk_search_fediverse">搜索联邦宇宙</string> <string name="sk_search_fediverse">搜索联邦宇宙</string>
<string name="sk_time_seconds">%d 秒</string> <string name="sk_time_seconds">%d 秒</string>
<string name="sk_settings_show_emoji_reactions_only_opened">仅当文被打开时</string> <string name="sk_settings_show_emoji_reactions_only_opened">仅当文被打开时</string>
<string name="sk_time_hours">%d 时</string> <string name="sk_time_hours">%d 时</string>
<string name="sk_settings_show_emoji_reactions_hide_empty">隐藏空的表情回应</string> <string name="sk_settings_show_emoji_reactions_hide_empty">隐藏空的表情回应</string>
<string name="sk_settings_show_emoji_reactions">在时间线中显示表情回应</string> <string name="sk_settings_show_emoji_reactions">在时间线中显示表情回应</string>
<plurals name="sk_posts_count_label"> <plurals name="sk_posts_count_label">
<item quantity="other"></item> <item quantity="other"></item>
</plurals> </plurals>
<string name="sk_suicide_search_terms">自杀</string> <string name="sk_suicide_search_terms">自杀</string>
<string name="sk_load_missing_posts_above">加载较新的</string> <string name="sk_load_missing_posts_above">加载较新的</string>
<string name="sk_time_days">%d 天</string> <string name="sk_time_days">%d 天</string>
<string name="sk_settings_show_emoji_reactions_always">始终显示添加按钮</string> <string name="sk_settings_show_emoji_reactions_always">始终显示添加按钮</string>
<string name="sk_search_suicide_hotlines">查找求助热线</string> <string name="sk_search_suicide_hotlines">查找求助热线</string>
<string name="sk_do_not_show_again">下次不再显示</string> <string name="sk_do_not_show_again">下次不再显示</string>
<string name="sk_trending_posts_info_banner">这些文正在联邦宇宙上引起关注。</string> <string name="sk_trending_posts_info_banner">这些文正在联邦宇宙上引起关注。</string>
<string name="sk_load_missing_posts_below">加载较旧的</string> <string name="sk_load_missing_posts_below">加载较旧的</string>
<string name="sk_search_suicide_title">以防你遇到困难…</string> <string name="sk_search_suicide_title">以防你遇到困难…</string>
<string name="sk_time_minutes">%d 分</string> <string name="sk_time_minutes">%d 分</string>
<string name="sk_search_suicide_message">如果你正在寻找一个不自杀的迹象,这就是。如果你遇到困难,请考虑拨打当地的自杀热线。</string> <string name="sk_search_suicide_message">如果你正在寻找一个不自杀的迹象,这就是。如果你遇到困难,请考虑拨打当地的自杀热线。</string>
<string name="sk_trending_links_info_banner">这些新闻故事正在联邦宇宙上被讨论。</string> <string name="sk_trending_links_info_banner">这些新闻故事正在联邦宇宙上被讨论。</string>
<string name="sk_post_contains_media">文包含媒体</string> <string name="sk_post_contains_media">文包含媒体</string>
<string name="sk_blocked_accounts">已屏蔽账号</string> <string name="sk_blocked_accounts">已屏蔽账号</string>
<string name="sk_muted_accounts">已静音账号</string> <string name="sk_muted_accounts">已静音账号</string>
<string name="sk_settings_like_icon">使用心形作为收藏图标</string> <string name="sk_settings_like_icon">使用心形作为收藏图标</string>

View file

@ -60,4 +60,41 @@
<string name="sk_color_palette_yellow">黃色</string> <string name="sk_color_palette_yellow">黃色</string>
<string name="sk_settings_translation_availability_note_unavailable">%s 似乎不支援翻譯.</string> <string name="sk_settings_translation_availability_note_unavailable">%s 似乎不支援翻譯.</string>
<string name="sk_lists_with_user">%s 所在的列表</string> <string name="sk_lists_with_user">%s 所在的列表</string>
<string name="sk_settings_color_palette_default">預設 (%s)</string>
<plurals name="sk_posts_count_label">
<item quantity="other">嘟文</item>
</plurals>
<string name="sk_quoting_user">引用自 %s</string>
<string name="sk_example_domain">example.social</string>
<string name="sk_settings_reply_visibility_all">所有回覆</string>
<string name="sk_settings_reply_visibility">回覆能見度</string>
<string name="sk_list">列表</string>
<string name="sk_post_contains_media">包含媒體的嘟文</string>
<string name="sk_clear_all_notifications_confirm_action">全部刪除</string>
<string name="sk_copy_link_to_post">複製嘟文連結</string>
<string name="sk_mark_as_draft">標記為草稿</string>
<string name="sk_announcements">公告</string>
<string name="sk_settings_enable_delete_notifications">允許刪除通知</string>
<string name="sk_clear_all_notifications">清除所有通知</string>
<string name="sk_settings_see_new_posts_button">「顯示新嘟文」按鈕</string>
<string name="sk_edit_timelines">編輯時間軸</string>
<string name="sk_quote_post">在新嘟文中引述</string>
<string name="sk_blocked_accounts">已封鎖的帳號</string>
<string name="sk_your_lists">您的清單</string>
<string name="sk_settings_donate">捐款</string>
<string name="sk_timeline_home">首頁</string>
<string name="sk_settings_lock_account">手動審查新的跟隨者</string>
<string name="sk_schedule_post">排定時間發佈</string>
<string name="sk_timeline_federated">聯邦時間軸</string>
<string name="sk_settings_instance">站臺</string>
<string name="sk_timeline_local">此伺服器</string>
<string name="sk_hashtag_timeline_local_only_switch">只顯示此伺服器的嘟文?</string>
<string name="sk_settings_uniform_icon_for_notifications">所有通知使用一致的圖示</string>
<string name="sk_muted_accounts">已靜音的帳號</string>
<string name="sk_settings_single_notification">只顯示一則通知</string>
<string name="sk_unsent_posts">未送出的嘟文</string>
<string name="sk_attach_file">附加檔案</string>
<string name="sk_search_fediverse">在聯邦宇宙中搜尋</string>
<string name="sk_tab_notifications">通知</string>
<string name="sk_settings_unifiedpush">使用 UnifiedPush</string>
</resources> </resources>

View file

@ -38,6 +38,7 @@
<attr name="colorM3DarkOnSurface" format="color" /> <attr name="colorM3DarkOnSurface" format="color" />
<attr name="colorTabBarAlpha" format="color" /> <attr name="colorTabBarAlpha" format="color" />
<attr name="colorFilledCardAlpha" format="color" /> <attr name="colorFilledCardAlpha" format="color" />
<attr name="colorM3Success" format="color" />
<attr name="toolbarActionButtonStyle" format="reference" /> <attr name="toolbarActionButtonStyle" format="reference" />
<attr name="colorPrimary25" format="color" /> <attr name="colorPrimary25" format="color" />

View file

@ -77,6 +77,7 @@
<item name="colorPoll">@color/bookmark_selected</item> <item name="colorPoll">@color/bookmark_selected</item>
<item name="colorTabBarAlpha">#14000000</item> <item name="colorTabBarAlpha">#14000000</item>
<item name="colorFilledCardAlpha">#22000000</item> <item name="colorFilledCardAlpha">#22000000</item>
<item name="colorM3Success">#FF5b8e63</item>
<item name="colorM3DisabledBackground">#1F1F1F1F</item> <item name="colorM3DisabledBackground">#1F1F1F1F</item>
<item name="colorM3Error">#B3261E</item> <item name="colorM3Error">#B3261E</item>
@ -162,6 +163,7 @@
<item name="colorM3OnErrorContainer">#F9DEDC</item> <item name="colorM3OnErrorContainer">#F9DEDC</item>
<item name="colorWhite">#000</item> <item name="colorWhite">#000</item>
<item name="colorSensitiveOverlay">#80000000</item> <item name="colorSensitiveOverlay">#80000000</item>
<item name="colorM3Success">#FF89bb9c</item>
</style> </style>
<style name="ColorPalette.Dark.TrueBlack"> <style name="ColorPalette.Dark.TrueBlack">

View file

@ -244,6 +244,13 @@
<string name="sk_icon_doctor">Doctor</string> <string name="sk_icon_doctor">Doctor</string>
<string name="sk_icon_diamond">Diamond</string> <string name="sk_icon_diamond">Diamond</string>
<string name="sk_icon_umbrella">Umbrella</string> <string name="sk_icon_umbrella">Umbrella</string>
<string name="sk_icon_water">Water</string>
<string name="sk_icon_sun">Sun</string>
<string name="sk_icon_sunset">Sunset</string>
<string name="sk_icon_cloud">Cloud</string>
<string name="sk_icon_thunderstorm">Thunderstorm</string>
<string name="sk_icon_rain">Rain</string>
<string name="sk_icon_snowflake">Snowflake</string>
<string name="sk_edit_timeline">Edit timeline</string> <string name="sk_edit_timeline">Edit timeline</string>
<string name="sk_add_timeline">Add timeline</string> <string name="sk_add_timeline">Add timeline</string>
<string name="sk_edit_timelines">Edit timelines</string> <string name="sk_edit_timelines">Edit timelines</string>
@ -417,4 +424,15 @@
<string name="sk_settings_lock_account">Manually approve new followers</string> <string name="sk_settings_lock_account">Manually approve new followers</string>
<string name="sk_settings_default_visibility">Default posting visibility</string> <string name="sk_settings_default_visibility">Default posting visibility</string>
<string name="sk_button_mutuals">Mutuals</string> <string name="sk_button_mutuals">Mutuals</string>
<string name="sk_private_note_hint">Add a personal note about this profile</string>
<string name="sk_confirm_changes">Confirm changes</string>
<string name="sk_private_note_update_failed">Failed to save note</string>
<string name="sk_private_note_confirm_delete">Delete personal note about %s?</string>
<string name="sk_delete_note">Delete personal note</string>
<string name="sk_add_note">Add personal note</string>
<string name="sk_settings_copy_crash_log">Copy latest crash log</string>
<string name="sk_settings_crash_log_unavailable">None available… yet</string>
<string name="sk_crash_log_copied">Crash log copied</string>
<string name="sk_open_post_preview">Preview post</string>
<string name="sk_post_preview">Preview</string>
</resources> </resources>

View file

@ -0,0 +1,6 @@
- Das neueste Absturzprotokoll wird gespeichert und kann auf der "Über Megalodon"-Einstellungs-Seite kopiert werden
- Auf Profilen können nun private Notizen angezeigt und erstellt werden
- Text-Änderungen werden im Bearbeitungs-Verlauf nun hervorgehoben
- Akkoma: Zitierte Beiträge werden nun in der Timeline angezeigt; Übersetzungen; Vorschau für Beiträge vorm Veröffentlichen
- Lesezeichen und Deine Favoriten gibt es nun als anpinnbare Timelines
- Diverse Fehlerbehebungen

View file

@ -0,0 +1,6 @@
- The latest crash log is now kept and ready to be copied from the "About Megalodon" settings page
- Add and display personal notes on profiles
- Text modifications are now highlighted in the edit history
- Akkoma: Quoted posts are displayed in the timeline; translation; preview posts before publishing
- Added Bookmarks and Your favorites as pinnable timelines
- Various bugfixes

View file

@ -0,0 +1,6 @@
- Нарешті: довгоочікуване оновлення інтерфейсу Material 3!
- Прогалини в домашній стрічці тепер дозволяють вибирати, завантажувати нові чи старі дані
- Для серверних оголошень і користувачів Akkoma: реакції емоджі!
- Нещодавно використані емоджі в інструменті вибору емоджі
- Займенники тепер відображаються поруч із іменами
- Багато виправлень та нових помилок, які ви можете виявити ✨

View file

@ -0,0 +1,6 @@
- 改进的过滤器,包括“隐藏警告”兼容性
- 重新设计的个人资料页面,元数据直接位于个人简介下方
- 对于很长的帖子可以折叠或展开
- 自动在回复的内容警告信息前加上“re:”的选项
- 隐藏时间线中交互按钮的选项
- 各种错误修正、调整和改进

View file

@ -0,0 +1,4 @@
- 长按 "关注 "按钮可关注其他账户中的配置文件
- 在其他账户中打开配置文件的选项
- 向下滚动时间轴时自动隐藏撰写按钮
- 修复打开服务器管理员配置文件时的崩溃问题

View file

@ -0,0 +1,5 @@
- 直接从通知栏点赞、收藏和回复
- 时间线中转发和回复的标题更美观、更一致
- 通知点(尚未实际加载通知)
- 针对 Akkoma 用户: 回复可见性、主题回复排序、引用...
- 崩溃修复和细微调整

View file

@ -0,0 +1,8 @@
- 改进后更清晰的主题视图
- 通过共享网站到 Megalodon 打开账户/帖子
- 从源实例加载追随者/收藏夹/......列表
- 改进非Mastodon服务器的兼容性
- 设置帖子内容类型的选项
- 支持从 Pixel 设备上的 "Recent" 应用程序复制 URL
- Auto-reveal equal CWs in threads
- 错误修复和用户界面改进