diff --git a/mastodon/src/main/AndroidManifest.xml b/mastodon/src/main/AndroidManifest.xml index ecfee8e21..54255565b 100644 --- a/mastodon/src/main/AndroidManifest.xml +++ b/mastodon/src/main/AndroidManifest.xml @@ -40,6 +40,22 @@ + + + + + + + + diff --git a/mastodon/src/main/assets/blocks.tsv b/mastodon/src/main/assets/blocks.tsv deleted file mode 100644 index 8b93b9dcf..000000000 --- a/mastodon/src/main/assets/blocks.tsv +++ /dev/null @@ -1,89 +0,0 @@ -# lists.d Mastodon Blocklist (c) 2022 Greyhat Academy LICENSED UNDER: CC-BY-NC-SA 4.0 -# https://raw.githubusercontent.com/greyhat-academy/lists.d/main/mastodon.domains.block.list.tsv -# This list contains domains of toxic mastodon instances -# Last-Modified: 1672044500 - -# gab - a neonazi social network -gab.ai -gab.com -gab.protohype.net - -# consequence-free speech -social.unzensiert.to -freeatlantis.com - -# reactionary bigotry and hatespeech against marginalized groups -poa.st -freespeechextremist.com -rdrama.cc -outpoa.st -anime.website -gameliberty.club -social.byoblu.com -yggdrasil.social -smuglo.li -dogeposting.social -unsafe.space -freezepeach.xyz - -# + CSAM -rojogato.com - -# antivaxxer shitposting & fearmongering -shadowsocial.org - -# Kiwifarms -kiwifarms.net -kiwifarms.cc -kiwifarms.is -kiwifarms.pleroma.net - - -# https://mastodon.art/@Curator/109649354849593592 - -poa.st antisemitic racist homophobic -nicecrew.digital antisemitic -beefyboys.win antisemitic racist homophobic harassment -cawfee.club antisemitic racist homophobic -comfyboy.club antisemitic racist homophobic -freespeechextremist.com racist homophobic -cum.salon racist misogynist -bae.st racist -natehiggers.online racist -rapemeat.solutions misogynist -rapist.town misogynist -rapefeminists.network misogynist -kiwifarms.cc harassment -noagendasocial.com noagenda -posting.lolicon.rocks underage -urchan.org harassment homophobic racist -ryona.agency harassment -yggdrasil.social antisemitic homophobic racist -genderheretics.xyz transphobic -baraag.net underage -lolison.top underage -shota.house underage -shota.social underage -aethy.com underage -taullo.social underage -childpawn.shop underage -posting.lolicon.rocks underage -loli.best underage -gothloli.club underage -smuglo.li underage -youjo.love underage -pedo.school underage -lolison.network underage -freak.university underage -mirr0r.city underage -xhais.love underage -refusal.biz underage -refusal.llc underage -mirr0r.city underage -nnia.space underage -ignorelist.com malicious -repl.co malicious - -# custom - -pawoo.net csam diff --git a/mastodon/src/main/assets/blocks.txt b/mastodon/src/main/assets/blocks.txt new file mode 100644 index 000000000..e6af4a505 --- /dev/null +++ b/mastodon/src/main/assets/blocks.txt @@ -0,0 +1,171 @@ +13bells.com +4aem.com +aethy.com +anime.website +annihilation.social +anon-kenkai.com +asbestos.cafe +bae.st +bajax.us +banepo.st +baraag.net +beefyboys.win +beepboop.ga +berserker.town +bikeshed.party +boks.moe +brainsoap.net +breastmilk.club +brighteon.social +cawfee.club +clew.lol +clubcyberia.co +collapsitarian.io +comfyboy.club +contrapointsfan.club +cum.camp +cum.salon +cybercriminal.eu +darknight-coffee.org +dembased.xyz +desupost.soy +detroitriotcity.com +eatthebugs.social +eientei.org +elementality.org +eveningzoo.club +firedragonstudios.com +firefaithfellowship.com +fluf.club +foxfam.club +freak.university +freeatlantis.com +freecumextremist.com +freedomstrike.org +freesoftwareextremist.com +freespeech.group +freespeechextremist.com +freetalklive.com +froth.zone +fulltermprivacy.com +gameliberty.club +gearlandia.haus +genderheretics.xyz +geofront.rocks +gleasonator.com +glee.li +glindr.org +goyim.app +goyslop.cafe +haeder.net +handholding.io +hidamari.apartments +hitchhiker.social +hunk.city +iddqd.social +intkos.link +justicewarrior.social +kawa-kun.com +kitsunemimi.club +kiwifarms.cc +kompost.cz +kurosawa.moe +leafposter.club +leftychan.net +lewdieheaven.com +liberdon.com +ligma.pro +lizards.live +lolicon.rocks +lolison.top +lovingexpressions.net +lucasvl.nl +mahodou.moe +makemysarcophagus.com +maladaptive.art +masochi.st +mastinator.com +merovingian.club +midwaytrades.com +mirr0r.city +moa.st +mouse.services +mugicha.club +narrativerry.xyz +natehiggers.online +neckbeard.xyz +needs.vodka +neenster.org +nicecrew.digital +nnia.space +noagendasocial.com +noagendasocial.nl +noagendatube.com +nobodyhasthe.biz +nukem.biz +obo.sh +onionfarms.org +outpoa.st +pawlicker.com +pawoo.net +pedo.school +piazza.today +pibvt.net +pieville.net +pisskey.io +plagu.ee +pmth.us +poa.st +poast.org +poast.tv +poster.place +prospeech.space +quodverum.com +rakket.app +rapemeat.solutions +rdrama.cc +rebelbase.site +retardedniggers.forsale +rojogato.com +ryona.agency +schwartzwelt.xyz +seal.cafe +shigusegubu.club +shitpost.cloud +shitposter.club +shota.house +silliness.observer +skinheads.eu +skinheads.io +skinheads.social +skinheads.uk +skippers-bin.com +skyshanty.xyz +slash.cl +sleepy.cafe +smuglo.li +sneed.social +sonichu.com +spinster.xyz +springbo.cc +starnix.network +stereophonic.space +strelizia.net +syspxl.xyz +tastingtraffic.net +teci.world +theapex.social +thepostearthdestination.com +tkammer.de +trumpislovetrumpis.life +truthsocial.co.in +urchan.org +varishangout.net +whinge.house +whinge.town +wideboys.org +wolfgirl.bar +xn--p1abe3d.xn--80asehdb +yggdrasil.social +youjo.love +zztails.gay diff --git a/mastodon/src/main/java/org/joinmastodon/android/ExitActivity.java b/mastodon/src/main/java/org/joinmastodon/android/ExitActivity.java new file mode 100644 index 000000000..54a5ccbc4 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ExitActivity.java @@ -0,0 +1,24 @@ +package org.joinmastodon.android; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +public class ExitActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + finishAndRemoveTask(); + } + + public static void exit(Context context) { + Intent intent = new Intent(context, ExitActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivity(intent); + } + +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java index 9819f1654..045c35699 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java +++ b/mastodon/src/main/java/org/joinmastodon/android/GlobalUserPreferences.java @@ -9,6 +9,7 @@ import android.os.Build; import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; +import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.TimelineDefinition; import java.lang.reflect.Type; @@ -61,10 +62,13 @@ public class GlobalUserPreferences{ private final static Type recentLanguagesType = new TypeToken>>() {}.getType(); private final static Type pinnedTimelinesType = new TypeToken>>() {}.getType(); + private final static Type accountsDefaultContentTypesType = new TypeToken>() {}.getType(); public static Map> recentLanguages; public static Map> pinnedTimelines; public static Set accountsWithLocalOnlySupport; public static Set accountsInGlitchMode; + public static Set accountsWithContentTypesEnabled; + public static Map accountsDefaultContentTypes; private final static Type recentEmojisType = new TypeToken>() {}.getType(); public static Map recentEmojis; @@ -133,6 +137,8 @@ public class GlobalUserPreferences{ accountsWithLocalOnlySupport=prefs.getStringSet("accountsWithLocalOnlySupport", new HashSet<>()); accountsInGlitchMode=prefs.getStringSet("accountsInGlitchMode", new HashSet<>()); replyVisibility=prefs.getString("replyVisibility", null); + accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>()); + accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>()); try { if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ @@ -192,6 +198,8 @@ public class GlobalUserPreferences{ .putStringSet("accountsWithLocalOnlySupport", accountsWithLocalOnlySupport) .putStringSet("accountsInGlitchMode", accountsInGlitchMode) .putString("replyVisibility", replyVisibility) + .putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled) + .putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes)) .apply(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/PanicResponderActivity.java b/mastodon/src/main/java/org/joinmastodon/android/PanicResponderActivity.java new file mode 100644 index 000000000..716907a67 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/PanicResponderActivity.java @@ -0,0 +1,49 @@ +package org.joinmastodon.android; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import org.joinmastodon.android.api.requests.oauth.RevokeOauthToken; +import org.joinmastodon.android.api.session.AccountSession; +import org.joinmastodon.android.api.session.AccountSessionManager; + +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; + + +public class PanicResponderActivity extends Activity { + public static final String PANIC_TRIGGER_ACTION = "info.guardianproject.panic.action.TRIGGER"; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Intent intent = getIntent(); + if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { + AccountSessionManager.getInstance().getLoggedInAccounts().forEach(accountSession -> logOut(accountSession.getID())); + ExitActivity.exit(this); + } + finishAndRemoveTask(); + } + + private void logOut(String accountID){ + AccountSession session=AccountSessionManager.getInstance().getAccount(accountID); + new RevokeOauthToken(session.app.clientId, session.app.clientSecret, session.token.accessToken) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(Object result){ + onLoggedOut(accountID); + } + + @Override + public void onError(ErrorResponse error){ + onLoggedOut(accountID); + } + }) + .exec(accountID); + } + + private void onLoggedOut(String accountID){ + AccountSessionManager.getInstance().removeAccount(accountID); + } +} \ No newline at end of file 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 56620eb48..7b7fc06e1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java @@ -60,7 +60,7 @@ public class MastodonAPIController{ thread.start(); try { final BufferedReader reader = new BufferedReader(new InputStreamReader( - MastodonApp.context.getAssets().open("blocks.tsv") + MastodonApp.context.getAssets().open("blocks.txt") )); String line; while ((line = reader.readLine()) != null) { @@ -91,7 +91,7 @@ public class MastodonAPIController{ Request.Builder builder=new Request.Builder() .url(req.getURL().toString()) .method(req.getMethod(), req.getRequestBody()) - .header("User-Agent", "MastodonAndroid/"+BuildConfig.VERSION_NAME); + .header("User-Agent", "MegalodonAndroid/"+BuildConfig.VERSION_NAME); String token=null; if(session!=null) 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 267617ff0..12a7935e3 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 @@ -1,6 +1,7 @@ package org.joinmastodon.android.api.requests.statuses; import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.StatusPrivacy; @@ -46,6 +47,7 @@ public class CreateStatus extends MastodonAPIRequest{ public String language; public String quoteId; + public ContentType contentType; public static class Poll{ public ArrayList options=new ArrayList<>(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusSourceText.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusSourceText.java index f1dd895e3..a84479075 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusSourceText.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/statuses/GetStatusSourceText.java @@ -3,6 +3,7 @@ package org.joinmastodon.android.api.requests.statuses; import org.joinmastodon.android.api.AllFieldsAreRequired; import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.model.BaseModel; +import org.joinmastodon.android.model.ContentType; public class GetStatusSourceText extends MastodonAPIRequest{ public GetStatusSourceText(String id){ @@ -14,5 +15,6 @@ public class GetStatusSourceText extends MastodonAPIRequestvisibilityPopup.show()); visibilityBtn.setOnTouchListener(visibilityPopup.getDragToOpenListener()); + buildContentTypePopup(contentTypeBtn); + contentTypeBtn.setOnClickListener(v->contentTypePopup.show()); + contentTypeBtn.setOnTouchListener(contentTypePopup.getDragToOpenListener()); + scheduleDraftDismiss.setOnClickListener(v->updateScheduledAt(null)); scheduleTimeBtn.setOnClickListener(v->pickScheduledDateTime()); @@ -489,8 +502,10 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr } } - if(editingStatus!=null && editingStatus.visibility!=null) { - statusVisibility=editingStatus.visibility; + if (savedInstanceState != null) { + statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility"); + } else if (editingStatus != null && editingStatus.visibility != null) { + statusVisibility = editingStatus.visibility; } else { loadDefaultStatusVisibility(savedInstanceState); } @@ -505,6 +520,20 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr }).setChecked(true); visibilityPopup.getMenu().findItem(R.id.local_only).setChecked(localOnly); + + if (savedInstanceState != null && savedInstanceState.containsKey("contentType")) { + contentType = (ContentType) savedInstanceState.getSerializable("contentType"); + } else if (getArguments().containsKey("sourceContentType")) { + try { + String val = getArguments().getString("sourceContentType"); + contentType = val == null ? null : ContentType.valueOf(val); + } catch (IllegalArgumentException ignored) {} + } + + int contentTypeId = ContentType.getContentTypeRes(contentType); + contentTypePopup.getMenu().findItem(contentTypeId).setChecked(true); + contentTypeBtn.setSelected(contentTypeId != R.id.content_type_null && contentTypeId != R.id.content_type_plain); + autocompleteViewController=new ComposeAutocompleteViewController(getActivity(), accountID); autocompleteViewController.setCompletionSelectedListener(this::onAutocompleteOptionSelected); View autocompleteView=autocompleteViewController.getView(); @@ -541,6 +570,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr outState.putParcelableArrayList("attachments", serializedAttachments); } outState.putSerializable("visibility", statusVisibility); + outState.putSerializable("contentType", contentType); if (scheduledAt != null) outState.putSerializable("scheduledAt", scheduledAt); if (scheduledStatus != null) outState.putParcelable("scheduledStatus", Parcels.wrap(scheduledStatus)); } @@ -948,6 +978,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr }); } + private int getContentTypeName(String id) { + return switch (id) { + case "text/plain" -> R.string.sk_content_type_plain; + case "text/html" -> R.string.sk_content_type_html; + case "text/markdown" -> R.string.sk_content_type_markdown; + case "text/bbcode" -> R.string.sk_content_type_bbcode; + case "text/x.misskeymarkdown" -> R.string.sk_content_type_mfm; + default -> throw new IllegalArgumentException("Invalid content type"); + }; + } + private void addBottomLanguage(Menu menu) { if (menu.findItem(allLanguages.size()) == null) { menu.add(0, allLanguages.size(), Menu.NONE, "bottom (\uD83E\uDD7A\uD83D\uDC49\uD83D\uDC48)"); @@ -1108,6 +1149,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr req.visibility=localOnly && instance.pleroma != null ? StatusPrivacy.LOCAL : statusVisibility; req.sensitive=sensitive; req.language=language; + req.contentType=contentType; req.scheduledAt = scheduledAt; if(!attachments.isEmpty()){ req.mediaIds=attachments.stream().map(a->a.serverAttachment.id).collect(Collectors.toList()); @@ -1963,16 +2005,38 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr }); } + @SuppressLint("ClickableViewAccessibility") + private void buildContentTypePopup(View btn) { + contentTypePopup=new PopupMenu(getActivity(), btn); + contentTypePopup.inflate(R.menu.compose_content_type); + Menu m = contentTypePopup.getMenu(); + ContentType.adaptMenuToInstance(m, instance); + if (contentType != null) m.findItem(R.id.content_type_null).setVisible(false); + + contentTypePopup.setOnMenuItemClickListener(i->{ + int id=i.getItemId(); + if (id == R.id.content_type_null) contentType = null; + else if (id == R.id.content_type_plain) contentType = ContentType.PLAIN; + else if (id == R.id.content_type_html) contentType = ContentType.HTML; + else if (id == R.id.content_type_markdown) contentType = ContentType.MARKDOWN; + else if (id == R.id.content_type_bbcode) contentType = ContentType.BBCODE; + else if (id == R.id.content_type_misskey_markdown) contentType = ContentType.MISSKEY_MARKDOWN; + else return false; + btn.setSelected(id != R.id.content_type_null && id != R.id.content_type_plain); + i.setChecked(true); + return true; + }); + + if (!GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)) { + btn.setVisibility(View.GONE); + } + } + private void loadDefaultStatusVisibility(Bundle savedInstanceState) { if(replyTo != null) { statusVisibility = (replyTo.visibility == StatusPrivacy.PUBLIC && GlobalUserPreferences.defaultToUnlistedReplies ? StatusPrivacy.UNLISTED : replyTo.visibility); } - // A saved privacy setting from a previous compose session wins over the reply visibility - if(savedInstanceState !=null){ - statusVisibility = (StatusPrivacy) savedInstanceState.getSerializable("visibility"); - } - AccountSessionManager asm = AccountSessionManager.getInstance(); Preferences prefs = asm.getAccount(accountID).preferences; if (prefs != null) { diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java index 58283c547..90927aefe 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java @@ -97,6 +97,8 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment items=new ArrayList<>(); private ThemeItem themeItem; private NotificationPolicyItem notificationPolicyItem; - private SwitchItem showNewPostsButtonItem, glitchModeItem, compactReblogReplyLineItem; + private SwitchItem showNewPostsItem, glitchModeItem, compactReblogReplyLineItem; + private ButtonItem defaultContentTypeButtonItem; private String accountID; private boolean needUpdateNotificationSettings; private boolean needAppRestart; @@ -97,7 +101,9 @@ public class SettingsFragment extends MastodonToolbarFragment{ private ImageView themeTransitionWindowView; private TextItem checkForUpdateItem, clearImageCacheItem; private ImageCache imageCache; + private Menu contentTypeMenu; + @SuppressLint("ClickableViewAccessibility") @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -290,15 +296,15 @@ public class SettingsFragment extends MastodonToolbarFragment{ })); items.add(new SwitchItem(R.string.sk_settings_load_new_posts, R.drawable.ic_fluent_arrow_sync_24_regular, GlobalUserPreferences.loadNewPosts, i->{ GlobalUserPreferences.loadNewPosts=i.checked; - showNewPostsButtonItem.enabled = i.checked; + showNewPostsItem.enabled = i.checked; if (!i.checked) { GlobalUserPreferences.showNewPostsButton = false; - showNewPostsButtonItem.checked = false; + showNewPostsItem.checked = false; } - if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsButtonItem)) instanceof SwitchViewHolder svh) svh.rebind(); + if (list.findViewHolderForAdapterPosition(items.indexOf(showNewPostsItem)) instanceof SwitchViewHolder svh) svh.rebind(); GlobalUserPreferences.save(); })); - items.add(showNewPostsButtonItem = new SwitchItem(R.string.sk_settings_show_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{ + items.add(showNewPostsItem = new SwitchItem(R.string.sk_settings_see_new_posts_button, R.drawable.ic_fluent_arrow_up_24_regular, GlobalUserPreferences.showNewPostsButton, i->{ GlobalUserPreferences.showNewPostsButton=i.checked; GlobalUserPreferences.save(); })); @@ -398,6 +404,36 @@ public class SettingsFragment extends MastodonToolbarFragment{ if (!TextUtils.isEmpty(instance.version)) items.add(new SmallTextItem(getString(R.string.sk_settings_server_version, instance.version))); items.add(new HeaderItem(R.string.sk_instance_features)); + items.add(new SwitchItem(R.string.sk_settings_content_types, 0, GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID), (i)->{ + if (i.checked) { + GlobalUserPreferences.accountsWithContentTypesEnabled.add(accountID); + if (GlobalUserPreferences.accountsDefaultContentTypes.get(accountID) == null) { + GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, ContentType.PLAIN); + } + } else { + GlobalUserPreferences.accountsWithContentTypesEnabled.remove(accountID); + GlobalUserPreferences.accountsDefaultContentTypes.remove(accountID); + } + if (list.findViewHolderForAdapterPosition(items.indexOf(defaultContentTypeButtonItem)) + instanceof ButtonViewHolder bvh) bvh.rebind(); + GlobalUserPreferences.save(); + })); + items.add(new SmallTextItem(getString(R.string.sk_settings_content_types_explanation))); + items.add(defaultContentTypeButtonItem = new ButtonItem(R.string.sk_settings_default_content_type, 0, b->{ + PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL); + popupMenu.inflate(R.menu.compose_content_type); + popupMenu.setOnMenuItemClickListener(item -> this.onContentTypeChanged(item, b)); + b.setOnTouchListener(popupMenu.getDragToOpenListener()); + b.setOnClickListener(v->popupMenu.show()); + ContentType contentType = GlobalUserPreferences.accountsDefaultContentTypes.get(accountID); + b.setText(getContentTypeString(contentType)); + contentTypeMenu = popupMenu.getMenu(); + contentTypeMenu.findItem(ContentType.getContentTypeRes(contentType)).setChecked(true); + ContentType.adaptMenuToInstance(contentTypeMenu, instance); + contentTypeMenu.findItem(R.id.content_type_null).setVisible( + !GlobalUserPreferences.accountsWithContentTypesEnabled.contains(accountID)); + })); + items.add(new SmallTextItem(getString(R.string.sk_settings_default_content_type_explanation))); items.add(new SwitchItem(R.string.sk_settings_support_local_only, 0, GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID), i->{ glitchModeItem.enabled = i.checked; if (i.checked) { @@ -617,6 +653,34 @@ public class SettingsFragment extends MastodonToolbarFragment{ } } + private @StringRes int getContentTypeString(@Nullable ContentType contentType) { + if (contentType == null) return R.string.sk_content_type_unspecified; + return switch (contentType) { + case PLAIN -> R.string.sk_content_type_plain; + case HTML -> R.string.sk_content_type_html; + case MARKDOWN -> R.string.sk_content_type_markdown; + case BBCODE -> R.string.sk_content_type_bbcode; + case MISSKEY_MARKDOWN -> R.string.sk_content_type_mfm; + }; + } + + private boolean onContentTypeChanged(MenuItem item, Button btn){ + int id = item.getItemId(); + ContentType contentType = switch (id) { + case R.id.content_type_plain -> ContentType.PLAIN; + case R.id.content_type_html -> ContentType.HTML; + case R.id.content_type_markdown -> ContentType.MARKDOWN; + case R.id.content_type_bbcode -> ContentType.BBCODE; + case R.id.content_type_misskey_markdown -> ContentType.MISSKEY_MARKDOWN; + default -> null; + }; + GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, contentType); + GlobalUserPreferences.save(); + btn.setText(getContentTypeString(contentType)); + item.setChecked(true); + return true; + } + private boolean onReplyVisibilityChanged(MenuItem item, Button btn){ String pref = null; int id = item.getItemId(); @@ -1146,7 +1210,11 @@ public class SettingsFragment extends MastodonToolbarFragment{ @Override public void onBind(ButtonItem item){ text.setText(item.text); - icon.setImageResource(item.icon); + if (item.icon == 0) { + icon.setVisibility(View.GONE); + } else { + icon.setImageResource(item.icon); + } item.buttonConsumer.accept(button); } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/ContentType.java b/mastodon/src/main/java/org/joinmastodon/android/model/ContentType.java new file mode 100644 index 000000000..55331b1f2 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/model/ContentType.java @@ -0,0 +1,40 @@ +package org.joinmastodon.android.model; + +import android.view.Menu; + +import androidx.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +import org.joinmastodon.android.R; + +public enum ContentType { + @SerializedName("text/plain") + PLAIN, + @SerializedName("text/html") + HTML, + @SerializedName("text/markdown") + MARKDOWN, + @SerializedName("text/bbcode") + BBCODE, // akkoma + @SerializedName("text/x.misskeymarkdown") + MISSKEY_MARKDOWN; // akkoma/*key + + public static int getContentTypeRes(@Nullable ContentType contentType) { + return contentType == null ? R.id.content_type_null : switch(contentType) { + case PLAIN -> R.id.content_type_plain; + case HTML -> R.id.content_type_html; + case MARKDOWN -> R.id.content_type_markdown; + case BBCODE -> R.id.content_type_bbcode; + case MISSKEY_MARKDOWN -> R.id.content_type_misskey_markdown; + }; + } + + public static void adaptMenuToInstance(Menu m, Instance i) { + if (i.pleroma == null) { + // memo: change this if glitch or another mastodon fork supports bbcode or mfm + m.findItem(R.id.content_type_bbcode).setVisible(false); + m.findItem(R.id.content_type_misskey_markdown).setVisible(false); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/ScheduledStatus.java b/mastodon/src/main/java/org/joinmastodon/android/model/ScheduledStatus.java index f4ee206f0..ebc56c8c7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/ScheduledStatus.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/ScheduledStatus.java @@ -39,6 +39,7 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{ public String idempotency; public String applicationId; public List mediaIds; + public ContentType contentType; } @Parcel diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index 4fd615f9b..3b29eb45d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -47,6 +47,7 @@ import org.joinmastodon.android.fragments.report.ReportReasonChoiceFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Announcement; import org.joinmastodon.android.model.Attachment; +import org.joinmastodon.android.model.ContentType; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.model.ScheduledStatus; @@ -223,6 +224,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ public void onSuccess(GetStatusSourceText.Response result){ args.putString("sourceText", result.text); args.putString("sourceSpoiler", result.spoilerText); + if (result.contentType != null) { + args.putString("sourceContentType", result.contentType.name()); + } if (redraft) { UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{ Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args); diff --git a/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_filled.xml new file mode 100644 index 000000000..642ab8c2c --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_filled.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_regular.xml new file mode 100644 index 000000000..96a2b4623 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_selector.xml b/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_selector.xml new file mode 100644 index 000000000..aabf6938c --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_text_edit_style_24_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/mastodon/src/main/res/layout/fragment_compose.xml b/mastodon/src/main/res/layout/fragment_compose.xml index 3a4838170..08238a73f 100644 --- a/mastodon/src/main/res/layout/fragment_compose.xml +++ b/mastodon/src/main/res/layout/fragment_compose.xml @@ -414,6 +414,19 @@ android:tooltipText="@string/post_visibility" android:src="@drawable/ic_fluent_earth_24_regular"/> + +