diff --git a/mastodon/build.gradle b/mastodon/build.gradle
index 514c47306..f3764ab86 100644
--- a/mastodon/build.gradle
+++ b/mastodon/build.gradle
@@ -81,7 +81,6 @@ android {
versionNameSuffix '-play'
}
githubRelease { initWith release }
- playRelease { initWith release }
fdroidRelease { initWith release }
}
compileOptions {
diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java b/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java
index 4728a4861..d9ea49e14 100644
--- a/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java
+++ b/mastodon/src/androidTest/java/org/joinmastodon/android/ui/utils/UiUtilsTest.java
@@ -257,5 +257,9 @@ public class UiUtilsTest {
assertEquals("* (asterisk)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
makeField("pronouns", "-- * (asterisk) --")
)).orElseThrow());
+
+ assertEquals("they/(she?)", UiUtils.extractPronouns(MastodonApp.context, fakeAccount(
+ makeField("pronouns", "they/(she?)...")
+ )).orElseThrow());
}
}
\ No newline at end of file
diff --git a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
index cb06d87a4..0625962ae 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/MainActivity.java
@@ -39,18 +39,44 @@ import org.joinmastodon.android.utils.ProvidesAssistContent;
import org.parceler.Parcels;
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.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
public class MainActivity extends FragmentStackActivity implements ProvidesAssistContent {
+ private static final String TAG="MainActivity";
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState){
AccountSession session=getCurrentSession();
UiUtils.setUserPreferredTheme(this, session);
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){
restartHomeFragment();
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java
index f62883b35..522f9a62c 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java
@@ -53,7 +53,9 @@ public class MastodonAPIController{
.registerTypeAdapter(Status.class, new Status.StatusDeserializer())
.create();
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 static List badDomains = new ArrayList<>();
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/AkkomaTranslateStatus.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/AkkomaTranslateStatus.java
new file mode 100644
index 000000000..f47f7c2c1
--- /dev/null
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/AkkomaTranslateStatus.java
@@ -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{
+ public AkkomaTranslateStatus(String id, String lang){
+ super(HttpMethod.GET, "/statuses/"+id+"/translations/"+lang.toUpperCase(), AkkomaTranslation.class);
+ }
+}
+
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java
index 52b93809b..6a241aa45 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/CreateStatus.java
@@ -48,6 +48,8 @@ public class CreateStatus extends MastodonAPIRequest{
public String quoteId;
public ContentType contentType;
+ public boolean preview;
+
public static class Poll{
public ArrayList options=new ArrayList<>();
public int expiresIn;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
index 3f9f1fe26..5d70e0b23 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSession.java
@@ -260,11 +260,13 @@ public class AccountSession{
}
private boolean isFilteredType(Status s){
+ AccountLocalPreferences localPreferences = getLocalPreferences();
return (!localPreferences.showReplies && s.inReplyToId != null)
|| (!localPreferences.showBoosts && s.reblog != null);
}
public void filterStatusContainingObjects(List objects, Function extractor, FilterContext context, Account profile){
+ AccountLocalPreferences localPreferences = getLocalPreferences();
if(!localPreferences.serverSideFiltersSupported) for(T obj:objects){
Status s=extractor.apply(obj);
if(s!=null && s.filtered!=null){
@@ -307,7 +309,7 @@ public class AccountSession{
if(isFilteredType(s) && (context == FilterContext.HOME || context == FilterContext.PUBLIC))
return true;
// 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){
if(filter.filter.isActive() && filter.filter.filterAction==FilterAction.HIDE)
return true;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
index ba90f5d80..33bd83ff3 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/api/session/AccountSessionManager.java
@@ -1,5 +1,7 @@
package org.joinmastodon.android.api.session;
+import static org.unifiedpush.android.connector.UnifiedPush.getDistributor;
+
import android.app.Activity;
import android.app.NotificationManager;
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.Instance;
import org.joinmastodon.android.model.Token;
+import org.unifiedpush.android.connector.UnifiedPush;
import java.io.File;
import java.io.FileInputStream;
@@ -112,6 +115,7 @@ public class AccountSessionManager{
}
public void addAccount(Instance instance, Token token, Account self, Application app, AccountActivationInfo activationInfo){
+ Context context = MastodonApp.context;
instances.put(instance.uri, instance);
AccountSession session=new AccountSession(token, self, app, instance.uri, activationInfo==null, activationInfo);
sessions.put(session.getID(), session);
@@ -124,7 +128,14 @@ public class AccountSessionManager{
MastodonAPIController.runInBackground(()->writeInstanceInfoFile(wrapper, 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);
}
maybeUpdateShortcuts();
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
index 26a8e6549..126054077 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java
@@ -23,13 +23,16 @@ import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.GlobalUserPreferences;
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.polls.SubmitPollVote;
+import org.joinmastodon.android.api.requests.statuses.AkkomaTranslateStatus;
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.events.PollUpdatedEvent;
import org.joinmastodon.android.model.Account;
+import org.joinmastodon.android.model.AkkomaTranslation;
import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Poll;
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.photoviewer.PhotoViewer;
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.PreviewlessMediaAttachmentViewController;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -66,6 +70,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.NonNull;
@@ -441,12 +446,14 @@ public abstract class BaseStatusListFragment exten
}
});
list.addItemDecoration(new StatusListItemDecoration());
+ list.addItemDecoration(new InsetStatusItemDecoration(this));
((UsableRecyclerView)list).setSelectorBoundsProvider(new UsableRecyclerView.SelectorBoundsProvider(){
private Rect tmpRect=new Rect();
@Override
public void getSelectorBounds(View view, Rect outRect){
- boolean hasDescendant = false, hasAncestor = false, isWarning = false;
- int lastIndex = -1, firstIndex = -1;
+ if(list!=view.getParent()) return;
+ boolean hasDescendant=false, hasAncestor=false, isWarning=false;
+ int lastIndex=-1, firstIndex=-1;
if(((UsableRecyclerView) list).isIncludeMarginsInItemHitbox()){
list.getDecoratedBoundsWithMargins(view, outRect);
}else{
@@ -576,7 +583,7 @@ public abstract class BaseStatusListFragment exten
spoilerFooterIndex=spoilerItem.contentItems.indexOf(pollItems.get(pollItems.size()-1));
}
pollItems.clear();
- StatusDisplayItem.buildPollItems(itemID, this, poll, pollItems, status);
+ StatusDisplayItem.buildPollItems(itemID, this, poll, status, pollItems);
if(spoilerItem!=null){
spoilerItem.contentItems.subList(spoilerFirstOptionIndex, spoilerFooterIndex+1).clear();
spoilerItem.contentItems.addAll(spoilerFirstOptionIndex, pollItems);
@@ -647,7 +654,8 @@ public abstract class BaseStatusListFragment exten
public void onRevealSpoilerClick(SpoilerStatusDisplayItem.Holder holder){
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) {
@@ -669,15 +677,16 @@ public abstract class BaseStatusListFragment exten
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;
if (!status.spoilerRevealed && !AccountSessionManager.get(accountID).getLocalPreferences().revealCWs)
status.sensitiveRevealed = false;
- SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
+ List spoilers=findAllHoldersOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
+ SpoilerStatusDisplayItem.Holder spoiler=spoilers.size() > 1 && isForQuote ? spoilers.get(1) : spoilers.get(0);
if(spoiler!=null) spoiler.rebind();
else notifyItemChanged(itemID, SpoilerStatusDisplayItem.class);
- SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(findItemOfType(itemID, SpoilerStatusDisplayItem.class));
+ SpoilerStatusDisplayItem spoilerItem=Objects.requireNonNull(spoiler.getItem());
int index=displayItems.indexOf(spoilerItem);
if(status.spoilerRevealed){
@@ -941,37 +950,52 @@ public abstract class BaseStatusListFragment exten
status.translationState=Status.TranslationState.SHOWN;
}else{
status.translationState=Status.TranslationState.LOADING;
- new TranslateStatus(status.getContentStatus().id, Locale.getDefault().getLanguage())
- .setCallback(new Callback<>(){
+ Consumer successCallback=(result)->{
+ status.translation=result;
+ status.translationState=Status.TranslationState.SHOWN;
+ 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)
- return;
- status.translation=result;
- status.translationState=Status.TranslationState.SHOWN;
- updateTranslation(itemID);
+ if(getActivity()!=null) successCallback.accept(result);
}
@Override
public void onError(ErrorResponse error){
- if(getActivity()==null)
- return;
- status.translationState=Status.TranslationState.HIDDEN;
- updateTranslation(itemID);
- new M3AlertDialogBuilder(getActivity())
- .setTitle(R.string.error)
- .setMessage(R.string.translation_failed)
- .setPositiveButton(R.string.ok, null)
- .show();
+ if(getActivity()!=null) translationCallbackError(status, itemID);
}
- })
- .exec(accountID);
+ });
+
+ // 1 minute
+ req.setTimeout(60000).exec(accountID);
}
}
}
updateTranslation(itemID);
}
+ private void translationCallbackError(Status status, String itemID) {
+ status.translationState=Status.TranslationState.HIDDEN;
+ updateTranslation(itemID);
+ new M3AlertDialogBuilder(getActivity())
+ .setTitle(R.string.error)
+ .setMessage(R.string.translation_failed)
+ .setPositiveButton(R.string.ok, null)
+ .show();
+ }
+
private void updateTranslation(String itemID) {
TextStatusDisplayItem.Holder text=findHolderOfType(itemID, TextStatusDisplayItem.Holder.class);
if(text!=null){
@@ -981,6 +1005,9 @@ public abstract class BaseStatusListFragment exten
notifyItemChanged(itemID, TextStatusDisplayItem.class);
}
+ if(isInstanceAkkoma())
+ return;
+
SpoilerStatusDisplayItem.Holder spoiler=findHolderOfType(itemID, SpoilerStatusDisplayItem.Holder.class);
if(spoiler!=null){
spoiler.rebind();
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
index 455887473..2f32b63df 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ComposeFragment.java
@@ -801,7 +801,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
String prefix = (GlobalUserPreferences.prefixReplies == ALWAYS
|| (GlobalUserPreferences.prefixReplies == TO_OTHERS && !ownID.equals(status.account.id)))
&& !status.spoilerText.startsWith("re: ") ? "re: " : "";
- spoilerEdit.setText(prefix + replyTo.spoilerText);
+ spoilerEdit.setText(prefix + status.spoilerText);
spoilerBtn.setSelected(true);
}
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));
}
-// draftsBtn = wrap.findViewById(R.id.drafts_btn);
- draftOptionsPopup = new PopupMenu(getContext(), draftsBtn);
+// draftsBtn=wrap.findViewById(R.id.drafts_btn);
+ draftOptionsPopup=new PopupMenu(getContext(), draftsBtn);
draftOptionsPopup.inflate(R.menu.compose_more);
- draftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.draft);
- undraftMenuItem = draftOptionsPopup.getMenu().findItem(R.id.undraft);
- scheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.schedule);
- unscheduleMenuItem = draftOptionsPopup.getMenu().findItem(R.id.unschedule);
+ Menu draftOptionsMenu=draftOptionsPopup.getMenu();
+ draftMenuItem=draftOptionsMenu.findItem(R.id.draft);
+ undraftMenuItem=draftOptionsMenu.findItem(R.id.undraft);
+ scheduleMenuItem=draftOptionsMenu.findItem(R.id.schedule);
+ unscheduleMenuItem=draftOptionsMenu.findItem(R.id.unschedule);
+ draftOptionsMenu.findItem(R.id.preview).setVisible(isInstanceAkkoma());
draftOptionsPopup.setOnMenuItemClickListener(i->{
- int id = i.getItemId();
- if (id == R.id.draft) updateScheduledAt(getDraftInstant());
- else if (id == R.id.schedule) pickScheduledDateTime();
- else if (id == R.id.unschedule || id == R.id.undraft) updateScheduledAt(null);
- else navigateToUnsentPosts();
+ int id=i.getItemId();
+ if(id==R.id.draft) updateScheduledAt(getDraftInstant());
+ else if(id==R.id.schedule) pickScheduledDateTime();
+ else if(id==R.id.unschedule || id==R.id.undraft) updateScheduledAt(null);
+ else if(id==R.id.drafts) navigateToUnsentPosts();
+ else if(id==R.id.preview) publish(true);
return true;
});
UiUtils.enablePopupMenuIcons(getContext(), draftOptionsPopup);
@@ -917,6 +920,8 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
return false;
});
+ if (!GlobalUserPreferences.relocatePublishButton):
+ publishButton.post(()->publishButton.setMinimumWidth(publishButton.getWidth()));
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setOnClickListener(v->{
Consumer draftCheckComplete=(isDraft)->{
@@ -1140,6 +1145,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
private void publish(){
+ publish(false);
+ }
+
+ private void publish(boolean preview){
sendingOverlay=new View(getActivity());
WindowManager.LayoutParams overlayParams=new WindowManager.LayoutParams();
overlayParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
@@ -1153,10 +1162,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
(GlobalUserPreferences.relocatePublishButton ? publishButtonRelocated : publishButton).setEnabled(false);
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();
CreateStatus.Request req=new CreateStatus.Request();
if("bottom".equals(postLang.encoding)){
@@ -1174,6 +1185,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
req.sensitive=sensitive;
req.contentType=contentType==ContentType.UNSPECIFIED ? null : contentType;
req.scheduledAt=scheduledAt;
+ req.preview=preview;
if(!mediaViewController.isEmpty()){
req.mediaIds=mediaViewController.getAttachmentIDs();
if(editingStatus != null){
@@ -1201,7 +1213,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
Callback resCallback=new Callback<>(){
@Override
public void onSuccess(Status result){
- maybeDeleteScheduledPost(() -> {
+ if(preview){
+ openPreview(result);
+ return;
+ }
+
+ maybeDeleteScheduledPost(()->{
wm.removeView(sendingOverlay);
sendingOverlay=null;
if(editingStatus==null || redraftStatus){
@@ -1223,10 +1240,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
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);
}
- if (getArguments().getBoolean("navigateToStatus", false)) {
+ if(getArguments().getBoolean("navigateToStatus", false)){
Bundle args=new Bundle();
args.putString("account", accountID);
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)
.setCallback(resCallback)
.exec(accountID);
- }else if(req.scheduledAt == null){
+ }else if(req.scheduledAt == null || preview){
new CreateStatus(req, uuid)
.setCallback(resCallback)
.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() {
if (postLang == null || postLang.language == null) return;
String language = postLang.language.getLanguage();
@@ -1665,6 +1701,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
}
contentTypePopup.setOnMenuItemClickListener(i->{
+ uuid=null;
int index=i.getItemId();
contentType=ContentType.values()[index];
btn.setSelected(index!=ContentType.UNSPECIFIED.ordinal() && index!=ContentType.PLAIN.ordinal());
diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
index 3243244df..31c100e5c 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditTimelinesFragment.java
@@ -67,86 +67,86 @@ import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView;
-public class EditTimelinesFragment extends MastodonRecyclerFragment implements ScrollableToTop {
- private String accountID;
- private TimelinesAdapter adapter;
- private final ItemTouchHelper itemTouchHelper;
- private Menu optionsMenu;
- private boolean updated;
- private final Map
") ? "" : "
", quoteUrl, quoteUrl);
- statusForContent.content += quoteInline;
- }
+ if(statusForContent.quote!=null) {
+ int quoteInlineIndex=statusForContent.content.lastIndexOf("
RE:");
+ if (quoteInlineIndex!=-1)
+ statusForContent.content=statusForContent.content.substring(0, quoteInlineIndex);
}
boolean hasSpoiler=!TextUtils.isEmpty(statusForContent.spoilerText);
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);
- 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);
}else if(!hasSpoiler && header!=null){
header.needBottomPadding=true;
@@ -276,9 +280,11 @@ public abstract class StatusDisplayItem{
}
PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments);
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);
- 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;
contentItems.add(mediaGrid);
}
@@ -295,17 +301,23 @@ public abstract class StatusDisplayItem{
}
}
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));
}
+ 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){
items.addAll(contentItems);
}
AccountLocalPreferences lp=fragment.getLocalPrefs();
- if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && lp.emojiReactionsEnabled &&
- (lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment)){
+ if((flags & FLAG_NO_EMOJI_REACTIONS)==0 && !status.preview && lp.emojiReactionsEnabled &&
+ (lp.showEmojiReactions!=ONLY_OPENED || fragment instanceof ThreadFragment) &&
+ statusForContent.reactions!=null){
boolean isMainStatus=fragment instanceof ThreadFragment t && t.getMainStatus().id.equals(statusForContent.id);
boolean showAddButton=lp.showEmojiReactions==ALWAYS || isMainStatus;
items.add(new EmojiReactionsStatusDisplayItem(parentID, fragment, statusForContent, accountID, !showAddButton, false));
@@ -317,8 +329,9 @@ public abstract class StatusDisplayItem{
items.add(footer);
}
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
- if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0){
+ if((inset || footer==null) && (flags & FLAG_CHECKABLE)==0 && !isForQuote){
items.add(new DummyStatusDisplayItem(parentID, fragment));
// 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)
@@ -330,12 +343,22 @@ public abstract class StatusDisplayItem{
items.add(gap=new GapStatusDisplayItem(parentID, fragment, status));
int i=1;
for(StatusDisplayItem item:items){
- item.inset=inset;
+ if(inset)
+ item.inset=true;
+ if(isForQuote){
+ item.status=statusForContent;
+ item.isForQuote=true;
+ }
item.index=i++;
}
if(items!=contentItems && !statusForContent.spoilerRevealed){
for(StatusDisplayItem item:contentItems){
- item.inset=inset;
+ if(inset)
+ item.inset=true;
+ if(isForQuote){
+ item.status=statusForContent;
+ item.isForQuote=true;
+ }
item.index=i++;
}
}
@@ -353,7 +376,7 @@ public abstract class StatusDisplayItem{
);
}
- public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List items, Status status){
+ public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, Status status, List items, Status status){
int i=0;
for(Poll.Option opt:poll.options){
items.add(new PollOptionStatusDisplayItem(parentID, poll, i, fragment, status));
@@ -390,12 +413,15 @@ public abstract class StatusDisplayItem{
}
public static abstract class Holder extends BindableViewHolder implements UsableRecyclerView.DisableableClickable{
+ private Context context;
+
public Holder(View itemView){
super(itemView);
}
public Holder(Context context, int layout, ViewGroup parent){
super(context, layout, parent);
+ this.context=context;
}
public String getItemID(){
@@ -404,6 +430,16 @@ public abstract class StatusDisplayItem{
@Override
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);
}
@@ -435,13 +471,13 @@ public abstract class StatusDisplayItem{
public boolean isLastDisplayItemForStatus(){
return getNextVisibleDisplayItem()
- .map(n->!n.parentID.equals(item.parentID))
+ .map(next->!next.parentID.equals(item.parentID) || item.inset && !next.inset)
.orElse(true);
}
@Override
public boolean isEnabled(){
- return item.parentFragment.isItemEnabled(item.parentID);
+ return item.parentFragment.isItemEnabled(item.parentID) || item.isForQuote;
}
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
index 5a562cb43..b1bc84ae2 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java
@@ -42,7 +42,6 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public boolean textSelectable;
public boolean reduceTopPadding;
public boolean disableTranslate;
- public final Status status;
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment, Status status, boolean disableTranslate){
super(parentID, parentFragment);
@@ -116,7 +115,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
text.setText(item.text);
}
text.setTextIsSelectable(false);
- if(item.textSelectable) itemView.post(() -> text.setTextIsSelectable(true));
+ if(item.textSelectable && !item.isForQuote) itemView.post(() -> text.setTextIsSelectable(true));
text.setInvalidateOnEveryFrame(false);
itemView.setClickable(false);
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);
if(next!=null && !next.parentID.equals(item.parentID)) next=null;
- int bottomPadding=next instanceof FooterStatusDisplayItem ? V.dp(6)
- : item.inset ? V.dp(12)
+ int bottomPadding=item.inset ? V.dp(12)
+ : next instanceof FooterStatusDisplayItem ? V.dp(6)
: (next instanceof EmojiReactionsStatusDisplayItem || next==null) ? 0
: V.dp(12);
itemView.setPadding(itemView.getPaddingLeft(), itemView.getPaddingTop(), itemView.getPaddingRight(), bottomPadding);
@@ -195,7 +194,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
public void updateTranslation(boolean updateText){
if(item.status==null)
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){
translationFooter=translationFooterStub.inflate();
translationInfo=findViewById(R.id.translation_info_text);
@@ -214,8 +213,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
String existingTransLang=existingTrans!=null ? existingTrans.detectedSourceLanguage : null;
String lang=existingTransLang!=null ? existingTransLang : item.status.getContentStatus().language;
Locale locale=lang!=null ? Locale.forLanguageTag(lang) : null;
- translationButton.setText(locale!=null
- ? item.parentFragment.getString(R.string.translate_post, locale.getDisplayLanguage())
+ String displayLang=locale==null || locale.getDisplayLanguage().isBlank() ? lang : locale.getDisplayLanguage();
+ translationButton.setText(displayLang!=null
+ ? item.parentFragment.getString(R.string.translate_post, displayLang)
: item.parentFragment.getString(R.string.sk_translate_post));
translationButton.setClickable(true);
translationButton.animate().alpha(1).setDuration(100).start();
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java
index a8e6d4751..37b9d5ec2 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java
@@ -18,7 +18,6 @@ import java.util.List;
// Mind the gap!
public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
public boolean loading;
- public final Status status;
public List filteredItems;
public LegacyFilter applyingFilter;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java
index 6f58dc121..481377181 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/DiffRemovedSpan.java
@@ -3,21 +3,21 @@ package org.joinmastodon.android.ui.text;
import android.text.TextPaint;
import android.text.style.CharacterStyle;
-import org.joinmastodon.android.ui.utils.UiUtils;
-
public class DiffRemovedSpan extends CharacterStyle {
private final String text;
+ private final int color;
- public DiffRemovedSpan(String text){
+ public DiffRemovedSpan(String text, int color){
this.text=text;
+ this.color=color;
}
@Override
public void updateDrawState(TextPaint tp) {
tp.setStrikeThruText(true);
- tp.setColor(0xFFCA5B63);
+ tp.setColor(color);
}
public String getText() {
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
index b4b58d120..d47942054 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/text/HtmlParser.java
@@ -70,6 +70,10 @@ public class HtmlParser{
private HtmlParser(){}
+ public static SpannableStringBuilder parse(String source, List emojis, List mentions, List tags, String accountID){
+ return parse(source, emojis, mentions, tags, accountID, null);
+ }
+
/**
* Parse HTML and custom emoji into a spanned string for display.
* Supported tags:
@@ -82,7 +86,7 @@ public class HtmlParser{
* @param emojis Custom emojis that are present in source as :code:
* @return a spanned string
*/
- public static SpannableStringBuilder parse(String source, List emojis, List mentions, List tags, String accountID){
+ public static SpannableStringBuilder parse(String source, List emojis, List mentions, List tags, String accountID, Context context){
class SpanInfo{
public Object span;
public int start;
@@ -107,6 +111,9 @@ public class HtmlParser{
Map tagsByTag=tags.stream().distinct().collect(Collectors.toMap(t->t.name.toLowerCase(), Function.identity()));
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(){
private final ArrayList openSpans=new ArrayList<>();
@@ -172,9 +179,9 @@ public class HtmlParser{
}
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));
- //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_removed" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text()), ssb.length(), el));
+ // fake elements for the edit history diff view
+ case "edit-diff-insert" -> openSpans.add(new SpanInfo(new ForegroundColorSpan(colorInsert), ssb.length(), el));
+ case "edit-diff-delete" -> openSpans.add(new SpanInfo(new DiffRemovedSpan(el.text(), colorDelete), ssb.length(), el));
}
}
}
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
index dcd0070fe..ac0029175 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java
@@ -1720,12 +1720,14 @@ public class UiUtils {
"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) {
if(!field.name.toLowerCase().contains(localizedPronouns) &&
!field.name.toLowerCase().contains("pronouns")) return null;
String text=HtmlParser.text(field.value);
- if(field.value.toLowerCase().contains("https://")){
+ if(text.toLowerCase().contains("https://")){
for(String pronounUrl : pronounsUrls){
int index=text.indexOf(pronounUrl);
int beginPronouns=index+pronounUrl.length();
@@ -1744,13 +1746,20 @@ public class UiUtils {
Matcher matcher=trimPronouns.matcher(text);
if(!matcher.find()) return null;
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()){
- if(c=='(') missingClosingParens++;
- if(c==')') missingClosingParens--;
+ if(c=='(') missingParens++;
+ 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(pronouns.matches("^.*\\s+:[a-zA-Z_]+$")) pronouns+=':';
return pronouns;
diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
index efd238071..caa89f797 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/ui/viewholders/AccountViewHolder.java
@@ -217,6 +217,7 @@ public class AccountViewHolder extends BindableViewHolder impl
Menu menu=contextMenu.getMenu();
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()));
MenuItem mute=menu.findItem(R.id.mute);
mute.setTitle(fragment.getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
diff --git a/mastodon/src/main/java/org/joinmastodon/android/utils/StatusTextEncoder.java b/mastodon/src/main/java/org/joinmastodon/android/utils/StatusTextEncoder.java
index 12f3d4891..7bf862178 100644
--- a/mastodon/src/main/java/org/joinmastodon/android/utils/StatusTextEncoder.java
+++ b/mastodon/src/main/java/org/joinmastodon/android/utils/StatusTextEncoder.java
@@ -1,9 +1,9 @@
package org.joinmastodon.android.utils;
-import android.text.TextUtils;
-
-import org.joinmastodon.android.fragments.ComposeFragment;
+import android.util.Pair;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
@@ -40,19 +40,22 @@ public class StatusTextEncoder {
}
// prettiest almost-exact replica of a pretty function
- public String decode(String content, Pattern regex) {
- Matcher m = regex.matcher(content);
- StringBuilder decodedString = new StringBuilder();
- int previousEnd = 0;
+ public Pair> decode(String content, Pattern regex) {
+ Matcher m=regex.matcher(content);
+ StringBuilder decodedString=new StringBuilder();
+ List decodedParts=new ArrayList<>();
+ int previousEnd=0;
while (m.find()) {
- MatchResult res = m.toMatchResult();
+ MatchResult res=m.toMatchResult();
// everything before the match - do not decode
decodedString.append(content.substring(previousEnd, res.start()));
- previousEnd = res.end();
+ previousEnd=res.end();
// 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));
- return decodedString.toString();
+ return Pair.create(decodedString.toString(), decodedParts);
}
}
diff --git a/mastodon/src/main/res/drawable/ic_fluent_cloud_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_cloud_24_regular.xml
new file mode 100644
index 000000000..e7bdc8906
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_cloud_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml
new file mode 100644
index 000000000..ad4287690
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_person_note_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_receipt_sparkles_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_receipt_sparkles_24_regular.xml
new file mode 100644
index 000000000..80cd0471c
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_receipt_sparkles_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_water_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_water_24_regular.xml
new file mode 100644
index 000000000..bf8cb5814
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_water_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_weather_rain_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_weather_rain_24_regular.xml
new file mode 100644
index 000000000..b34e1d9b0
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_weather_rain_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_weather_snowflake_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_weather_snowflake_24_regular.xml
new file mode 100644
index 000000000..e2f13dec3
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_weather_snowflake_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_weather_sunny_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_weather_sunny_24_regular.xml
new file mode 100644
index 000000000..9292857cf
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_weather_sunny_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_weather_sunny_low_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_weather_sunny_low_24_regular.xml
new file mode 100644
index 000000000..06112f857
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_weather_sunny_low_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/drawable/ic_fluent_weather_thunderstorm_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_weather_thunderstorm_24_regular.xml
new file mode 100644
index 000000000..3a6b081a7
--- /dev/null
+++ b/mastodon/src/main/res/drawable/ic_fluent_weather_thunderstorm_24_regular.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/mastodon/src/main/res/layout/display_item_header_checkable.xml b/mastodon/src/main/res/layout/display_item_header_checkable.xml
index 7b51fa150..5c8ffc70c 100644
--- a/mastodon/src/main/res/layout/display_item_header_checkable.xml
+++ b/mastodon/src/main/res/layout/display_item_header_checkable.xml
@@ -1,40 +1,39 @@
-
-
+ android:layout_width="56dp"
+ android:layout_height="match_parent"
+ android:paddingTop="16dp">
-
+
-
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/mastodon/src/main/res/layout/fragment_profile.xml b/mastodon/src/main/res/layout/fragment_profile.xml
index 12e0f3021..909a2e91d 100644
--- a/mastodon/src/main/res/layout/fragment_profile.xml
+++ b/mastodon/src/main/res/layout/fragment_profile.xml
@@ -13,7 +13,7 @@
@@ -80,11 +80,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/cover"
- android:layout_alignParentEnd="true">
+ android:layout_alignParentEnd="true"
+ android:clipChildren="false">
@@ -95,7 +97,8 @@
style="@style/Widget.Mastodon.M3.Button.Tonal"
android:background="@drawable/bg_button_m3_tonal_circle_selector"
android:paddingStart="12dp"
- android:drawableStart="@drawable/ic_fluent_alert_24_selector" />
+ android:drawableStart="@drawable/ic_fluent_alert_24_selector"
+ tools:ignore="RtlSymmetry" />
@@ -219,7 +223,7 @@
android:layout_below="@id/username"
android:id="@+id/note_edit_wrap"
android:layout_marginTop="4dp"
- android:layout_marginBottom="16dp"
+ android:layout_marginBottom="12dp"
android:layout_marginHorizontal="16dp"
android:visibility="gone">
@@ -227,32 +231,44 @@
android:id="@+id/note_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingVertical="16dp"
- android:inputType="textMultiLine|textCapSentences"
+ android:minHeight="52dp"
+ android:paddingVertical="15dp"
+ android:textColor="?colorM3OnSurface"
+ android:inputType="text|textMultiLine|textCapSentences"
android:singleLine="false"
- android:drawablePadding="12dp"
- android:drawableTint="?android:textColorSecondary"
android:background="@drawable/bg_note_edit"
- android:paddingEnd="48dp"
- android:paddingHorizontal="16dp"
+ android:paddingEnd="52dp"
+ android:paddingStart="20dp"
android:elevation="0dp"
- android:visibility="gone"
- android:hint="@string/mo_personal_note"/>
+ android:hint="@string/mo_personal_note"
+ tools:ignore="RtlSymmetry" />
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+ tools:text="Eugen" />
diff --git a/mastodon/src/main/res/menu/compose_more.xml b/mastodon/src/main/res/menu/compose_more.xml
index a47109276..3671739ff 100644
--- a/mastodon/src/main/res/menu/compose_more.xml
+++ b/mastodon/src/main/res/menu/compose_more.xml
@@ -5,4 +5,5 @@
+
diff --git a/mastodon/src/main/res/menu/profile.xml b/mastodon/src/main/res/menu/profile.xml
index aaaf7cae8..6d93f8a60 100644
--- a/mastodon/src/main/res/menu/profile.xml
+++ b/mastodon/src/main/res/menu/profile.xml
@@ -1,6 +1,9 @@