Compare commits
47 Commits
v1.2.3+for
...
v1.2.3+for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b94741feae | ||
|
|
e43d6c35d8 | ||
|
|
4a6f9e80b1 | ||
|
|
ec02680507 | ||
|
|
5fc569a45a | ||
|
|
4bc9c5691d | ||
|
|
19b68855ac | ||
|
|
70fdfb612e | ||
|
|
0a32c217d8 | ||
|
|
5dfa9237ad | ||
|
|
573ff75498 | ||
|
|
87c37df370 | ||
|
|
7fb0944e66 | ||
|
|
35c8a3d121 | ||
|
|
9e58413d1a | ||
|
|
90e60aef84 | ||
|
|
8547ce05ed | ||
|
|
0825faee5c | ||
|
|
d43a697df7 | ||
|
|
3b742c4391 | ||
|
|
a43a396043 | ||
|
|
bcb4fac553 | ||
|
|
35bf858a83 | ||
|
|
870bfaf08c | ||
|
|
c4238fb19b | ||
|
|
ba7aeb358b | ||
|
|
6f3fd4d454 | ||
|
|
c890195567 | ||
|
|
b50a327b17 | ||
|
|
97547f334f | ||
|
|
1ab953d819 | ||
|
|
dbe7eb25ff | ||
|
|
45ecec09f5 | ||
|
|
9b4556d293 | ||
|
|
307d483a56 | ||
|
|
9612248695 | ||
|
|
1f63401e5b | ||
|
|
d35ec18a88 | ||
|
|
b93b1847c3 | ||
|
|
cd46ed565f | ||
|
|
4a0e4edef8 | ||
|
|
2ea7333daa | ||
|
|
fa7a66809d | ||
|
|
71884ab760 | ||
|
|
f31205c670 | ||
|
|
0091ae87ce | ||
|
|
ad13b1e927 |
@@ -15,8 +15,8 @@ android {
|
||||
applicationId "org.joinmastodon.android.sk"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 90
|
||||
versionName "1.2.3+fork.90"
|
||||
versionCode 92
|
||||
versionName "1.2.3+fork.92"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resourceConfigurations += ['ar-rSA', 'ar-rDZ', 'be-rBY', 'bn-rBD', 'bs-rBA', 'ca-rES', 'cs-rCZ', 'da-rDK', 'de-rDE', 'el-rGR', 'es-rES', 'eu-rES', 'fa-rIR', 'fi-rFI', 'fil-rPH', 'fr-rFR', 'ga-rIE', 'gd-rGB', 'gl-rES', 'hi-rIN', 'hr-rHR', 'hu-rHU', 'hy-rAM', 'ig-rNG', 'in-rID', 'is-rIS', 'it-rIT', 'iw-rIL', 'ja-rJP', 'kab', 'ko-rKR', 'my-rMM', 'nl-rNL', 'no-rNO', 'oc-rFR', 'pl-rPL', 'pt-rBR', 'pt-rPT', 'ro-rRO', 'ru-rRU', 'si-rLK', 'sl-rSI', 'sv-rSE', 'th-rTH', 'tr-rTR', 'uk-rUA', 'ur-rIN', 'vi-rVN', 'zh-rCN', 'zh-rTW']
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.fragments.ComposeFragment;
|
||||
@@ -43,7 +44,7 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
finish();
|
||||
} else if (isOpenable || sessions.size() > 1) {
|
||||
AccountSwitcherSheet sheet = new AccountSwitcherSheet(this, null, true, isOpenable);
|
||||
if (isOpenable) sheet.setOnClick((accountId, open) -> {
|
||||
sheet.setOnClick((accountId, open) -> {
|
||||
if (open && text.isPresent()) {
|
||||
BiConsumer<Class<? extends Fragment>, Bundle> callback = (clazz, args) -> {
|
||||
if (clazz == null) {
|
||||
@@ -59,8 +60,17 @@ public class ExternalShareActivity extends FragmentStackActivity{
|
||||
finish();
|
||||
startActivity(intent);
|
||||
};
|
||||
if (isFediUrl) UiUtils.lookupURL(this, accountId, text.get(), false, callback);
|
||||
else UiUtils.lookupAccountHandle(this, accountId, fediHandle.get(), callback);
|
||||
|
||||
fediHandle
|
||||
.<MastodonAPIRequest<?>>map(handle ->
|
||||
UiUtils.lookupAccountHandle(this, accountId, handle, callback))
|
||||
.or(() ->
|
||||
UiUtils.lookupURL(this, accountId, text.get(), callback))
|
||||
.ifPresent(req ->
|
||||
req.wrapProgress(this, R.string.loading, true, d -> {
|
||||
UiUtils.transformDialogForLookup(this, accountId, isFediUrl ? text.get() : null, d);
|
||||
d.setOnDismissListener((ev) -> finish());
|
||||
}));
|
||||
} else {
|
||||
openComposeFragment(accountId);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ public class GlobalUserPreferences{
|
||||
public static boolean compactReblogReplyLine;
|
||||
public static boolean confirmBeforeReblog;
|
||||
public static boolean allowRemoteLoading;
|
||||
public static AutoRevealMode autoRevealEqualSpoilers;
|
||||
public static String publishButtonText;
|
||||
public static ThemePreference theme;
|
||||
public static ColorPreference color;
|
||||
@@ -129,6 +130,7 @@ public class GlobalUserPreferences{
|
||||
accountsWithContentTypesEnabled=prefs.getStringSet("accountsWithContentTypesEnabled", new HashSet<>());
|
||||
accountsDefaultContentTypes=fromJson(prefs.getString("accountsDefaultContentTypes", null), accountsDefaultContentTypesType, new HashMap<>());
|
||||
allowRemoteLoading=prefs.getBoolean("allowRemoteLoading", true);
|
||||
autoRevealEqualSpoilers=AutoRevealMode.valueOf(prefs.getString("autoRevealEqualSpoilers", AutoRevealMode.THREADS.name()));
|
||||
|
||||
try {
|
||||
color=ColorPreference.valueOf(prefs.getString("color", ColorPreference.PINK.name()));
|
||||
@@ -179,6 +181,7 @@ public class GlobalUserPreferences{
|
||||
.putStringSet("accountsWithContentTypesEnabled", accountsWithContentTypesEnabled)
|
||||
.putString("accountsDefaultContentTypes", gson.toJson(accountsDefaultContentTypes))
|
||||
.putBoolean("allowRemoteLoading", allowRemoteLoading)
|
||||
.putString("autoRevealEqualSpoilers", autoRevealEqualSpoilers.name())
|
||||
.apply();
|
||||
}
|
||||
|
||||
@@ -198,4 +201,10 @@ public class GlobalUserPreferences{
|
||||
LIGHT,
|
||||
DARK
|
||||
}
|
||||
|
||||
public enum AutoRevealMode {
|
||||
NEVER,
|
||||
THREADS,
|
||||
DISCUSSIONS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +127,8 @@ public class MainActivity extends FragmentStackActivity implements ProvidesAssis
|
||||
}
|
||||
|
||||
private void showFragmentForExternalShare(Bundle args) {
|
||||
String clazz = args.getString("fromExternalShare");
|
||||
Fragment fragment = switch (clazz) {
|
||||
String className = args.getString("fromExternalShare");
|
||||
Fragment fragment = switch (className) {
|
||||
case "ThreadFragment" -> new ThreadFragment();
|
||||
case "ProfileFragment" -> new ProfileFragment();
|
||||
default -> null;
|
||||
|
||||
@@ -61,6 +61,9 @@ public class OAuthActivity extends Activity{
|
||||
@Override
|
||||
public void onSuccess(Token token){
|
||||
new GetOwnAccount()
|
||||
// in case the instance (looking at pixelfed) wants to redirect to a
|
||||
// website, we need to pass a context so we can launch a browser
|
||||
.setContext(OAuthActivity.this)
|
||||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Account account){
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.joinmastodon.android.api.gson.IsoInstantTypeAdapter;
|
||||
import org.joinmastodon.android.api.gson.IsoLocalDateTypeAdapter;
|
||||
import org.joinmastodon.android.api.session.AccountSession;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.utils.UiUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -161,6 +162,11 @@ public class MastodonAPIController{
|
||||
respObj=gson.fromJson(reader, req.respClass);
|
||||
}
|
||||
}catch(JsonIOException|JsonSyntaxException x){
|
||||
if (req.context != null && response.body().contentType().subtype().equals("html")) {
|
||||
UiUtils.launchWebBrowser(req.context, response.request().url().toString());
|
||||
req.cancel();
|
||||
return;
|
||||
}
|
||||
if(BuildConfig.DEBUG)
|
||||
Log.w(TAG, "["+(session==null ? "no-auth" : session.getID())+"] "+response+" error parsing or reading body", x);
|
||||
req.onError(x.getLocalizedMessage(), response.code(), x);
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.joinmastodon.android.api;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
@@ -50,6 +51,7 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
Map<String, String> headers;
|
||||
private ProgressDialog progressDialog;
|
||||
protected boolean removeUnsupportedItems;
|
||||
@Nullable Context context;
|
||||
|
||||
public MastodonAPIRequest(HttpMethod method, String path, Class<T> respClass){
|
||||
this.path=path;
|
||||
@@ -181,6 +183,16 @@ public abstract class MastodonAPIRequest<T> extends APIRequest<T>{
|
||||
return this;
|
||||
}
|
||||
|
||||
public MastodonAPIRequest<T> setContext(Context context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
public void validateAndPostprocessResponse(T respObj, Response httpResponse) throws IOException{
|
||||
if(respObj instanceof BaseModel){
|
||||
|
||||
@@ -754,6 +754,13 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
|
||||
assistContent.setWebUri(getWebUri(getSession().getInstanceUri().buildUpon()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDataLoaded(List<T> d, boolean more) {
|
||||
super.onDataLoaded(d, more);
|
||||
// more available, but the page isn't even full yet? seems wrong, let's load some more
|
||||
if (more && d.size() < itemsPerPage) preloader.onScrolledToLastItem();
|
||||
}
|
||||
|
||||
protected class DisplayItemsAdapter extends UsableRecyclerView.Adapter<BindableViewHolder<StatusDisplayItem>> implements ImageLoaderRecyclerAdapter{
|
||||
|
||||
public DisplayItemsAdapter(){
|
||||
|
||||
@@ -149,7 +149,7 @@ import me.grishka.appkit.imageloader.ViewImageLoader;
|
||||
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
|
||||
import me.grishka.appkit.utils.V;
|
||||
|
||||
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener{
|
||||
public class ComposeFragment extends MastodonToolbarFragment implements OnBackPressedListener, ComposeEditText.SelectionListener, HasAccountID {
|
||||
|
||||
private static final int MEDIA_RESULT=717;
|
||||
private static final int IMAGE_DESCRIPTION_RESULT=363;
|
||||
@@ -355,6 +355,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
} else {
|
||||
mediaBtn.setOnClickListener(v -> openFilePicker(false));
|
||||
}
|
||||
if (isInstancePixelfed()) pollBtn.setVisibility(View.GONE);
|
||||
pollBtn.setOnClickListener(v->togglePoll());
|
||||
emojiBtn.setOnClickListener(v->emojiKeyboard.toggleKeyboardPopup(mainEditText));
|
||||
spoilerBtn.setOnClickListener(v->toggleSpoiler());
|
||||
@@ -847,12 +848,18 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
updateScheduledAt(scheduledAt != null ? scheduledAt : scheduledStatus != null ? scheduledStatus.scheduledAt : null);
|
||||
buildLanguageSelector(languageButton);
|
||||
|
||||
if (editingStatus != null && scheduledStatus == null) {
|
||||
if (isInstancePixelfed()) spoilerBtn.setVisibility(View.GONE);
|
||||
if (isInstancePixelfed() || (editingStatus != null && scheduledStatus == null)) {
|
||||
// editing an already published post
|
||||
draftsBtn.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountID() {
|
||||
return accountID;
|
||||
}
|
||||
|
||||
private void navigateToUnsentPosts() {
|
||||
Bundle args=new Bundle();
|
||||
args.putString("account", accountID);
|
||||
@@ -1009,7 +1016,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
if(att.state!=AttachmentUploadState.DONE)
|
||||
nonDoneAttachmentCount++;
|
||||
}
|
||||
publishButton.setEnabled((trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||
publishButton.setEnabled((!isInstancePixelfed() || attachments.size() > 0) && (trimmedCharCount>0 || !attachments.isEmpty()) && charCount<=charLimit && nonDoneAttachmentCount==0 && (pollOptions.isEmpty() || nonEmptyPollOptionsCount>1));
|
||||
sendError.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -1151,7 +1158,7 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
sendProgress.setVisibility(View.VISIBLE);
|
||||
sendError.setVisibility(View.GONE);
|
||||
|
||||
Callback<Status> resCallback=new Callback<>(){
|
||||
Callback<Status> resCallback = new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(Status result){
|
||||
maybeDeleteScheduledPost(() -> {
|
||||
@@ -1164,7 +1171,17 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
E.post(new StatusCountersUpdatedEvent(replyTo));
|
||||
}
|
||||
}else{
|
||||
E.post(new StatusUpdatedEvent(result));
|
||||
// pixelfed doesn't return the edited status :/
|
||||
Status editedStatus = result == null ? editingStatus : result;
|
||||
if (result == null) {
|
||||
editedStatus.text = req.status;
|
||||
editedStatus.spoilerText = req.spoilerText;
|
||||
editedStatus.sensitive = req.sensitive;
|
||||
editedStatus.language = req.language;
|
||||
// user will have to reload to see html
|
||||
editedStatus.content = req.status;
|
||||
}
|
||||
E.post(new StatusUpdatedEvent(editedStatus));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isStateSaved()) {
|
||||
Nav.finish(ComposeFragment.this);
|
||||
@@ -1899,9 +1916,12 @@ public class ComposeFragment extends MastodonToolbarFragment implements OnBackPr
|
||||
visibilityPopup=new PopupMenu(getActivity(), v);
|
||||
visibilityPopup.inflate(R.menu.compose_visibility);
|
||||
Menu m=visibilityPopup.getMenu();
|
||||
if (isInstancePixelfed()) {
|
||||
m.findItem(R.id.vis_private).setVisible(false);
|
||||
}
|
||||
MenuItem localOnlyItem = visibilityPopup.getMenu().findItem(R.id.local_only);
|
||||
boolean prefsSaysSupported = GlobalUserPreferences.accountsWithLocalOnlySupport.contains(accountID);
|
||||
if (instance.isAkkoma()) {
|
||||
if (isInstanceAkkoma()) {
|
||||
m.findItem(R.id.vis_local).setVisible(true);
|
||||
} else if (localOnly || prefsSaysSupported) {
|
||||
localOnlyItem.setVisible(true);
|
||||
|
||||
@@ -17,6 +17,10 @@ public interface HasAccountID {
|
||||
return getInstance().map(Instance::isAkkoma).orElse(false);
|
||||
}
|
||||
|
||||
default boolean isInstancePixelfed() {
|
||||
return getInstance().map(Instance::isPixelfed).orElse(false);
|
||||
}
|
||||
|
||||
default Optional<Instance> getInstance() {
|
||||
return getSession().getInstance();
|
||||
}
|
||||
|
||||
@@ -384,10 +384,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
UiUtils.lookupAccountHandle(getContext(), accountID, remoteAccount.getFullyQualifiedName(), (c, args) -> {
|
||||
if (getContext() == null) return;
|
||||
if (args == null || !args.containsKey("profileAccount")) {
|
||||
onError(new MastodonErrorResponse(
|
||||
getContext().getString(R.string.sk_error_loading_profile),
|
||||
0, null
|
||||
));
|
||||
Toast.makeText(getContext(), getContext().getString(
|
||||
R.string.sk_error_loading_profile, domain
|
||||
), Toast.LENGTH_SHORT).show();
|
||||
Nav.finish(this);
|
||||
return;
|
||||
}
|
||||
onAccountLoaded(Parcels.unwrap(args.getParcelable("profileAccount")));
|
||||
@@ -654,8 +654,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
|
||||
));
|
||||
}
|
||||
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getShortUsername()));
|
||||
if(isOwnProfile)
|
||||
if(isOwnProfile) {
|
||||
if (isInstancePixelfed()) menu.findItem(R.id.scheduled).setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
MenuItem mute = menu.findItem(R.id.mute);
|
||||
mute.setTitle(getString(relationship.muting ? R.string.unmute_user : R.string.mute_user, account.getShortUsername()));
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.squareup.otto.Subscribe;
|
||||
import org.joinmastodon.android.BuildConfig;
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.ColorPreference;
|
||||
import org.joinmastodon.android.MainActivity;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
@@ -85,8 +86,8 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
private ArrayList<Item> items=new ArrayList<>();
|
||||
private ThemeItem themeItem;
|
||||
private NotificationPolicyItem notificationPolicyItem;
|
||||
private SwitchItem showNewPostsItem, glitchModeItem, compactReblogReplyLineItem;
|
||||
private ButtonItem defaultContentTypeButtonItem;
|
||||
private SwitchItem showNewPostsItem, glitchModeItem, compactReblogReplyLineItem, alwaysRevealSpoilersItem;
|
||||
private ButtonItem defaultContentTypeButtonItem, autoRevealSpoilersItem;
|
||||
private String accountID;
|
||||
private boolean needUpdateNotificationSettings;
|
||||
private boolean needAppRestart;
|
||||
@@ -189,9 +190,18 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
GlobalUserPreferences.showInteractionCounts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
items.add(alwaysRevealSpoilersItem = new SwitchItem(R.string.sk_settings_always_reveal_content_warnings, R.drawable.ic_fluent_chat_warning_24_regular, GlobalUserPreferences.alwaysExpandContentWarnings, i->{
|
||||
GlobalUserPreferences.alwaysExpandContentWarnings=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(autoRevealSpoilersItem)) instanceof ButtonViewHolder bvh) bvh.rebind();
|
||||
}));
|
||||
items.add(autoRevealSpoilersItem = new ButtonItem(R.string.sk_settings_auto_reveal_equal_spoilers, R.drawable.ic_fluent_eye_24_regular, b->{
|
||||
PopupMenu popupMenu=new PopupMenu(getActivity(), b, Gravity.CENTER_HORIZONTAL);
|
||||
popupMenu.inflate(R.menu.settings_auto_reveal_spoiler);
|
||||
popupMenu.setOnMenuItemClickListener(i -> onAutoRevealSpoilerClick(i, b));
|
||||
b.setOnTouchListener(popupMenu.getDragToOpenListener());
|
||||
b.setOnClickListener(v->popupMenu.show());
|
||||
onAutoRevealSpoilerChanged(b);
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_tabs_disable_swipe, R.drawable.ic_fluent_swipe_right_24_regular, GlobalUserPreferences.disableSwipe, i->{
|
||||
GlobalUserPreferences.disableSwipe=i.checked;
|
||||
@@ -276,7 +286,7 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
GlobalUserPreferences.collapseLongPosts=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
}));
|
||||
items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_eye_24_regular, GlobalUserPreferences.spectatorMode, i->{
|
||||
items.add(new SwitchItem(R.string.sk_settings_hide_interaction, R.drawable.ic_fluent_star_off_24_regular, GlobalUserPreferences.spectatorMode, i->{
|
||||
GlobalUserPreferences.spectatorMode=i.checked;
|
||||
GlobalUserPreferences.save();
|
||||
needAppRestart=true;
|
||||
@@ -531,6 +541,36 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean onAutoRevealSpoilerClick(MenuItem item, Button btn) {
|
||||
int id = item.getItemId();
|
||||
|
||||
AutoRevealMode mode = AutoRevealMode.NEVER;
|
||||
if (id == R.id.auto_reveal_threads) mode = AutoRevealMode.THREADS;
|
||||
else if (id == R.id.auto_reveal_discussions) mode = AutoRevealMode.DISCUSSIONS;
|
||||
|
||||
GlobalUserPreferences.alwaysExpandContentWarnings = false;
|
||||
GlobalUserPreferences.autoRevealEqualSpoilers = mode;
|
||||
GlobalUserPreferences.save();
|
||||
onAutoRevealSpoilerChanged(btn);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onAutoRevealSpoilerChanged(Button b) {
|
||||
if (GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
b.setText(R.string.sk_settings_auto_reveal_anyone);
|
||||
} else {
|
||||
b.setText(switch(GlobalUserPreferences.autoRevealEqualSpoilers){
|
||||
case THREADS -> R.string.sk_settings_auto_reveal_author;
|
||||
case DISCUSSIONS -> R.string.sk_settings_auto_reveal_anyone;
|
||||
default -> R.string.sk_settings_auto_reveal_nobody;
|
||||
});
|
||||
if (alwaysRevealSpoilersItem.checked != GlobalUserPreferences.alwaysExpandContentWarnings) {
|
||||
alwaysRevealSpoilersItem.checked = GlobalUserPreferences.alwaysExpandContentWarnings;
|
||||
if (list.findViewHolderForAdapterPosition(items.indexOf(alwaysRevealSpoilersItem)) instanceof SwitchViewHolder svh) svh.rebind();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTrueBlackThemeChanged(SwitchItem item){
|
||||
GlobalUserPreferences.trueBlackTheme=item.checked;
|
||||
GlobalUserPreferences.save();
|
||||
@@ -560,14 +600,14 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
ContentType contentType = null;
|
||||
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;
|
||||
|
||||
GlobalUserPreferences.accountsDefaultContentTypes.put(accountID, contentType);
|
||||
GlobalUserPreferences.save();
|
||||
btn.setText(getContentTypeString(contentType));
|
||||
|
||||
@@ -164,13 +164,29 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>
|
||||
protected void removeStatus(Status status){
|
||||
data.remove(status);
|
||||
preloadedData.remove(status);
|
||||
int index=-1;
|
||||
int index=-1, ancestorFirstIndex = -1, ancestorLastIndex = -1;
|
||||
for(int i=0;i<displayItems.size();i++){
|
||||
if(status.id.equals(displayItems.get(i).parentID)){
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if(status.id.equals(item.parentID)){
|
||||
index=i;
|
||||
break;
|
||||
}
|
||||
if (item.parentID.equals(status.inReplyToId)) {
|
||||
if (ancestorFirstIndex == -1) ancestorFirstIndex = i;
|
||||
ancestorLastIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// did we find an ancestor that is also the status' neighbor?
|
||||
if (ancestorFirstIndex >= 0 && ancestorLastIndex == index - 1) {
|
||||
for (int i = ancestorFirstIndex; i <= ancestorLastIndex; i++) {
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
// update ancestor to have no descendant anymore
|
||||
if (item.parentID.equals(status.inReplyToId)) item.hasDescendantNeighbor = false;
|
||||
}
|
||||
adapter.notifyItemRangeChanged(ancestorFirstIndex, ancestorLastIndex - ancestorFirstIndex + 1);
|
||||
}
|
||||
|
||||
if(index==-1)
|
||||
return;
|
||||
int lastIndex;
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.GlobalUserPreferences.AutoRevealMode;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusByID;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
|
||||
@@ -37,6 +39,7 @@ import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -102,22 +105,26 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
footer.hideCounts=true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int deleteThisItem : deleteTheseItems) itemsToModify.remove(deleteThisItem);
|
||||
if(s.id.equals(mainStatus.id)) {
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, s.getContentStatus()));
|
||||
items.add(new ExtendedFooterStatusDisplayItem(s.id, this, accountID, s.getContentStatus()));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoadData(int offset, int count){
|
||||
loadMainStatus();
|
||||
if (refreshing) loadMainStatus();
|
||||
currentRequest=new GetStatusContext(mainStatus.id)
|
||||
.setCallback(new SimpleCallback<>(this){
|
||||
@Override
|
||||
public void onSuccess(StatusContext result){
|
||||
if (getContext() == null) return;
|
||||
Map<String, Status> oldData = null;
|
||||
if(refreshing){
|
||||
oldData = new HashMap<>(data.size());
|
||||
for (Status s : data) oldData.put(s.id, s);
|
||||
data.clear();
|
||||
ancestryMap.clear();
|
||||
displayItems.clear();
|
||||
@@ -149,6 +156,23 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
adapter.notifyItemRemoved(prependedCount);
|
||||
count--;
|
||||
}
|
||||
|
||||
for (Status s : data) {
|
||||
Status oldStatus = oldData == null ? null : oldData.get(s.id);
|
||||
// restore previous spoiler/filter revealed states when refreshing
|
||||
if (oldStatus != null) {
|
||||
s.spoilerRevealed = oldStatus.spoilerRevealed;
|
||||
s.filterRevealed = oldStatus.filterRevealed;
|
||||
} else if (GlobalUserPreferences.autoRevealEqualSpoilers != AutoRevealMode.NEVER &&
|
||||
s.spoilerText != null &&
|
||||
s.spoilerText.equals(mainStatus.spoilerText) &&
|
||||
mainStatus.spoilerRevealed) {
|
||||
if (GlobalUserPreferences.autoRevealEqualSpoilers == AutoRevealMode.DISCUSSIONS || Objects.equals(mainStatus.account.id, s.account.id)) {
|
||||
s.spoilerRevealed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataLoaded();
|
||||
if(refreshing){
|
||||
refreshDone();
|
||||
@@ -187,6 +211,10 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
protected Object maybeApplyMainStatus() {
|
||||
if (updatedStatus == null || !contextInitiallyRendered) return null;
|
||||
|
||||
// restore revealed states for main status because it gets updated after doLoadData
|
||||
updatedStatus.filterRevealed = mainStatus.filterRevealed;
|
||||
updatedStatus.spoilerRevealed = mainStatus.spoilerRevealed;
|
||||
|
||||
// returning fired event object to facilitate testing
|
||||
Object event;
|
||||
if (updatedStatus.editedAt != null &&
|
||||
@@ -310,15 +338,65 @@ public class ThreadFragment extends StatusListFragment implements ProvidesAssist
|
||||
}
|
||||
|
||||
protected void onStatusCreated(StatusCreatedEvent ev){
|
||||
if(ev.status.inReplyToId!=null && getStatusByID(ev.status.inReplyToId)!=null){
|
||||
data.add(ev.status);
|
||||
onAppendItems(Collections.singletonList(ev.status));
|
||||
if (ev.status.inReplyToId == null) return;
|
||||
Status repliedToStatus = getStatusByID(ev.status.inReplyToId);
|
||||
if (repliedToStatus == null) return;
|
||||
NeighborAncestryInfo ancestry = ancestryMap.get(repliedToStatus.id);
|
||||
|
||||
int nextDisplayItemsIndex = -1, indexOfPreviousDisplayItem = -1;
|
||||
|
||||
for (int i = 0; i < displayItems.size(); i++) {
|
||||
StatusDisplayItem item = displayItems.get(i);
|
||||
if (repliedToStatus.id.equals(item.parentID)) {
|
||||
// saving the replied-to status' display items index to eventually reach the last one
|
||||
indexOfPreviousDisplayItem = i;
|
||||
item.hasDescendantNeighbor = true;
|
||||
} else if (indexOfPreviousDisplayItem >= 0 && nextDisplayItemsIndex == -1) {
|
||||
// previous display item was the replied-to status' display items
|
||||
nextDisplayItemsIndex = i;
|
||||
// nothing left to do if there's no other reply to that status
|
||||
if (ancestry.descendantNeighbor == null) break;
|
||||
}
|
||||
if (ancestry.descendantNeighbor != null && item.parentID.equals(ancestry.descendantNeighbor.id)) {
|
||||
// existing reply shall no longer have the replied-to status as its neighbor
|
||||
item.hasAncestoringNeighbor = false;
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to inserting the item at the end
|
||||
nextDisplayItemsIndex = nextDisplayItemsIndex >= 0 ? nextDisplayItemsIndex : displayItems.size();
|
||||
int nextDataIndex = data.indexOf(repliedToStatus) + 1;
|
||||
|
||||
// if replied-to status already has another reply...
|
||||
if (ancestry.descendantNeighbor != null) {
|
||||
// update the reply's ancestry to remove its ancestoring neighbor (as we did above)
|
||||
ancestryMap.get(ancestry.descendantNeighbor.id).ancestoringNeighbor = null;
|
||||
// make sure the existing reply has a reply line
|
||||
if (nextDataIndex < data.size() &&
|
||||
!(displayItems.get(nextDisplayItemsIndex) instanceof ReblogOrReplyLineStatusDisplayItem)) {
|
||||
Status nextStatus = data.get(nextDataIndex);
|
||||
if (!nextStatus.account.id.equals(repliedToStatus.account.id)) {
|
||||
// create reply line manually since we're not building that status' items
|
||||
displayItems.add(nextDisplayItemsIndex, StatusDisplayItem.buildReplyLine(
|
||||
this, nextStatus, accountID, nextStatus, repliedToStatus.account, false
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update replied-to status' ancestry
|
||||
ancestry.descendantNeighbor = ev.status;
|
||||
|
||||
// add ancestry for newly created status before building its display items
|
||||
ancestryMap.put(ev.status.id, new NeighborAncestryInfo(ev.status, null, repliedToStatus));
|
||||
displayItems.addAll(nextDisplayItemsIndex, buildDisplayItems(ev.status));
|
||||
data.add(nextDataIndex, ev.status);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemEnabled(String id){
|
||||
return !id.equals(mainStatus.id);
|
||||
return !id.equals(mainStatus.id) || !mainStatus.filterRevealed;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -257,7 +257,7 @@ public abstract class BaseAccountListFragment extends RecyclerFragment<BaseAccou
|
||||
|
||||
public void bindRelationship(){
|
||||
Relationship rel=relationships.get(item.account.id);
|
||||
if(rel==null || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
||||
if(rel==null || item.account.isRemote || AccountSessionManager.getInstance().isSelf(accountID, item.account)){
|
||||
button.setVisibility(View.GONE);
|
||||
}else{
|
||||
button.setVisibility(View.VISIBLE);
|
||||
|
||||
@@ -65,7 +65,6 @@ public class Account extends BaseModel implements Searchable{
|
||||
/**
|
||||
* An image banner that is shown above the profile and in profile cards.
|
||||
*/
|
||||
@RequiredField
|
||||
public String header;
|
||||
/**
|
||||
* A static version of the header. Equal to header if its value is a static image; different if header is an animated GIF.
|
||||
|
||||
@@ -148,6 +148,10 @@ public class Instance extends BaseModel{
|
||||
return pleroma != null;
|
||||
}
|
||||
|
||||
public boolean isPixelfed() {
|
||||
return version.contains("compatible; Pixelfed");
|
||||
}
|
||||
|
||||
public boolean hasFeature(Feature feature) {
|
||||
Optional<List<String>> pleromaFeatures = Optional.ofNullable(pleroma)
|
||||
.map(p -> p.metadata)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.joinmastodon.android.model;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.model.Poll.Option;
|
||||
import org.parceler.Parcel;
|
||||
@@ -16,7 +17,6 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
public Instant scheduledAt;
|
||||
@RequiredField
|
||||
public Params params;
|
||||
@RequiredField
|
||||
public List<Attachment> mediaAttachments;
|
||||
|
||||
@Override
|
||||
@@ -24,8 +24,17 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException {
|
||||
super.postprocess();
|
||||
if (mediaAttachments == null) mediaAttachments = List.of();
|
||||
for(Attachment a:mediaAttachments)
|
||||
a.postprocess();
|
||||
if (params != null) params.postprocess();
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class Params {
|
||||
public static class Params extends BaseModel {
|
||||
@RequiredField
|
||||
public String text;
|
||||
public String spoilerText;
|
||||
@@ -40,10 +49,16 @@ public class ScheduledStatus extends BaseModel implements DisplayItemsParent{
|
||||
public String applicationId;
|
||||
public List<String> mediaIds;
|
||||
public ContentType contentType;
|
||||
|
||||
@Override
|
||||
public void postprocess() throws ObjectValidationException {
|
||||
super.postprocess();
|
||||
if (poll != null) poll.postprocess();
|
||||
}
|
||||
}
|
||||
|
||||
@Parcel
|
||||
public static class ScheduledPoll {
|
||||
public static class ScheduledPoll extends BaseModel {
|
||||
@RequiredField
|
||||
public String expiresIn;
|
||||
@RequiredField
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.google.gson.JsonParseException;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
|
||||
import org.joinmastodon.android.ui.text.HtmlParser;
|
||||
import org.parceler.Parcel;
|
||||
@@ -20,8 +21,6 @@ import java.lang.reflect.Type;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@Parcel
|
||||
public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
@RequiredField
|
||||
@@ -39,7 +38,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
public boolean sensitive;
|
||||
@RequiredField
|
||||
public String spoilerText;
|
||||
@RequiredField
|
||||
public List<Attachment> mediaAttachments;
|
||||
public Application application;
|
||||
@RequiredField
|
||||
@@ -96,6 +94,7 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
t.postprocess();
|
||||
for(Emoji e:emojis)
|
||||
e.postprocess();
|
||||
if (mediaAttachments == null) mediaAttachments = List.of();
|
||||
for(Attachment a:mediaAttachments)
|
||||
a.postprocess();
|
||||
account.postprocess();
|
||||
@@ -173,6 +172,12 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{
|
||||
return strippedText;
|
||||
}
|
||||
|
||||
public boolean isReblogPermitted(String accountID){
|
||||
return visibility.isReblogPermitted(account.id.equals(
|
||||
AccountSessionManager.getInstance().getAccount(accountID).self.id
|
||||
));
|
||||
}
|
||||
|
||||
public static Status ofFake(String id, String text, Instant createdAt) {
|
||||
Status s = new Status();
|
||||
s.id = id;
|
||||
|
||||
@@ -14,7 +14,7 @@ public enum StatusPrivacy{
|
||||
@SerializedName("local")
|
||||
LOCAL(4); // akkoma
|
||||
|
||||
private int privacy;
|
||||
private final int privacy;
|
||||
|
||||
StatusPrivacy(int privacy) {
|
||||
this.privacy = privacy;
|
||||
@@ -24,6 +24,13 @@ public enum StatusPrivacy{
|
||||
return privacy > other.getPrivacy();
|
||||
}
|
||||
|
||||
public boolean isReblogPermitted(boolean isOwnStatus){
|
||||
return (this == StatusPrivacy.PUBLIC ||
|
||||
this == StatusPrivacy.UNLISTED ||
|
||||
this == StatusPrivacy.LOCAL ||
|
||||
(this == StatusPrivacy.PRIVATE && isOwnStatus));
|
||||
}
|
||||
|
||||
public int getPrivacy() {
|
||||
return privacy;
|
||||
}
|
||||
|
||||
@@ -75,8 +75,7 @@ public class AccountSwitcherSheet extends BottomSheet{
|
||||
this.fragment=fragment;
|
||||
this.externalShare = externalShare;
|
||||
this.openInApp = openInApp;
|
||||
this.onClick = onClick;
|
||||
|
||||
|
||||
accounts=AccountSessionManager.getInstance().getLoggedInAccounts().stream().map(WrappedAccount::new).collect(Collectors.toList());
|
||||
|
||||
list=new UsableRecyclerView(activity);
|
||||
|
||||
@@ -33,12 +33,14 @@ import me.grishka.appkit.Nav;
|
||||
|
||||
public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public final Status status;
|
||||
public final String accountID;
|
||||
|
||||
private static final DateTimeFormatter TIME_FORMATTER=DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT);
|
||||
|
||||
public ExtendedFooterStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
|
||||
public ExtendedFooterStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, String accountID, Status status){
|
||||
super(parentID, parentFragment);
|
||||
this.status=status;
|
||||
this.accountID=accountID;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,7 +74,9 @@ public class ExtendedFooterStatusDisplayItem extends StatusDisplayItem{
|
||||
public void onBind(ExtendedFooterStatusDisplayItem item){
|
||||
Status s=item.status;
|
||||
favorites.setText(context.getResources().getQuantityString(R.plurals.x_favorites, (int)(s.favouritesCount%1000), s.favouritesCount));
|
||||
reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int)(s.reblogsCount%1000), s.reblogsCount));
|
||||
reblogs.setText(context.getResources().getQuantityString(R.plurals.x_reblogs, (int) (s.reblogsCount % 1000), s.reblogsCount));
|
||||
reblogs.setVisibility(s.isReblogPermitted(item.accountID) ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(s.editedAt!=null){
|
||||
editHistory.setVisibility(View.VISIBLE);
|
||||
editHistory.setText(UiUtils.formatRelativeTimestampAsMinutesAgo(itemView.getContext(), s.editedAt));
|
||||
|
||||
@@ -133,8 +133,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{
|
||||
boost.setSelected(item.status.reblogged);
|
||||
favorite.setSelected(item.status.favourited);
|
||||
bookmark.setSelected(item.status.bookmarked);
|
||||
boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL
|
||||
|| (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id)));
|
||||
boost.setEnabled(item.status.isReblogPermitted(item.accountID));
|
||||
|
||||
int nextPos = getAbsoluteAdapterPosition() + 1;
|
||||
boolean nextIsWarning = item.parentFragment.getDisplayItems().size() > nextPos &&
|
||||
|
||||
@@ -196,7 +196,14 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
|
||||
args.putBoolean("navigateToStatus", true);
|
||||
}
|
||||
}
|
||||
if(!redraft && TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText)){
|
||||
boolean isPixelfed = item.parentFragment.isInstancePixelfed();
|
||||
boolean textEmpty = TextUtils.isEmpty(item.status.content) && TextUtils.isEmpty(item.status.spoilerText);
|
||||
if(!redraft && (isPixelfed || textEmpty)){
|
||||
// pixelfed doesn't support /statuses/:id/source :/
|
||||
if (isPixelfed) {
|
||||
args.putString("sourceText", HtmlParser.text(item.status.content));
|
||||
args.putString("sourceSpoiler", item.status.spoilerText);
|
||||
}
|
||||
Nav.go(item.parentFragment.getActivity(), ComposeFragment.class, args);
|
||||
}else if(item.scheduledStatus!=null){
|
||||
args.putString("sourceText", item.status.text);
|
||||
|
||||
@@ -105,6 +105,21 @@ public abstract class StatusDisplayItem{
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
|
||||
}
|
||||
|
||||
public static ReblogOrReplyLineStatusDisplayItem buildReplyLine(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parent, Account account, boolean threadReply) {
|
||||
String parentID = parent.getID();
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
return new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
);
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
||||
String parentID=parentObject.getID();
|
||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||
@@ -120,17 +135,7 @@ public abstract class StatusDisplayItem{
|
||||
|
||||
if(statusForContent.inReplyToAccountId!=null && !(threadReply && fragment instanceof ThreadFragment)){
|
||||
Account account = knownAccounts.get(statusForContent.inReplyToAccountId);
|
||||
String text = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: GlobalUserPreferences.compactReblogReplyLine && status.reblog != null ? account.displayName
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
String fullText = threadReply ? fragment.getString(R.string.sk_show_thread)
|
||||
: account == null ? fragment.getString(R.string.sk_in_reply)
|
||||
: fragment.getString(R.string.in_reply_to, account.displayName);
|
||||
replyLine = new ReblogOrReplyLineStatusDisplayItem(
|
||||
parentID, fragment, text, account == null ? List.of() : account.emojis,
|
||||
R.drawable.ic_fluent_arrow_reply_20sp_filled, null, null, fullText
|
||||
);
|
||||
replyLine = buildReplyLine(fragment, status, accountID, parentObject, account, threadReply);
|
||||
}
|
||||
|
||||
if(status.reblog!=null){
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package org.joinmastodon.android.ui.text;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.fonts.FontFamily;
|
||||
import android.graphics.fonts.FontStyle;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.LeadingMarginSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
@@ -17,13 +13,10 @@ import android.text.style.SubscriptSpan;
|
||||
import android.text.style.SuperscriptSpan;
|
||||
import android.text.style.TypefaceSpan;
|
||||
import android.text.style.UnderlineSpan;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.twitter.twittertext.Regex;
|
||||
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.model.Emoji;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.Mention;
|
||||
@@ -251,6 +244,10 @@ public class HtmlParser{
|
||||
return Jsoup.clean(html, Safelist.none());
|
||||
}
|
||||
|
||||
public static String text(String html) {
|
||||
return Jsoup.parse(html).body().wholeText();
|
||||
}
|
||||
|
||||
public static CharSequence parseLinks(String text){
|
||||
Matcher matcher=URL_PATTERN.matcher(text);
|
||||
if(!matcher.find()) // Return the original string if there are no URLs
|
||||
|
||||
@@ -56,6 +56,7 @@ import org.joinmastodon.android.E;
|
||||
import org.joinmastodon.android.GlobalUserPreferences;
|
||||
import org.joinmastodon.android.MastodonApp;
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.MastodonAPIRequest;
|
||||
import org.joinmastodon.android.api.MastodonErrorResponse;
|
||||
import org.joinmastodon.android.api.StatusInteractionController;
|
||||
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
|
||||
@@ -1052,50 +1053,46 @@ public class UiUtils {
|
||||
}, null);
|
||||
}
|
||||
|
||||
public static void lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer<Status> resultConsumer) {
|
||||
lookup(context, queryStatus, targetAccountID, sourceAccountID, GetSearchResults.Type.STATUSES, resultConsumer, results ->
|
||||
public static Optional<MastodonAPIRequest<SearchResults>> lookupStatus(Context context, Status queryStatus, String targetAccountID, @Nullable String sourceAccountID, Consumer<Status> resultConsumer) {
|
||||
return lookup(context, queryStatus, targetAccountID, sourceAccountID, GetSearchResults.Type.STATUSES, resultConsumer, results ->
|
||||
!results.statuses.isEmpty() ? Optional.of(results.statuses.get(0)) : Optional.empty()
|
||||
);
|
||||
}
|
||||
|
||||
public static void lookupAccount(Context context, Account queryAccount, String targetAccountID, @Nullable String sourceAccountID, Consumer<Account> resultConsumer) {
|
||||
lookup(context, queryAccount, targetAccountID, sourceAccountID, GetSearchResults.Type.ACCOUNTS, resultConsumer, results ->
|
||||
public static Optional<MastodonAPIRequest<SearchResults>> lookupAccount(Context context, Account queryAccount, String targetAccountID, @Nullable String sourceAccountID, Consumer<Account> resultConsumer) {
|
||||
return lookup(context, queryAccount, targetAccountID, sourceAccountID, GetSearchResults.Type.ACCOUNTS, resultConsumer, results ->
|
||||
!results.accounts.isEmpty() ? Optional.of(results.accounts.get(0)) : Optional.empty()
|
||||
);
|
||||
}
|
||||
|
||||
public static <T extends Searchable> void lookup(Context context, T query, String targetAccountID, @Nullable String sourceAccountID, @Nullable GetSearchResults.Type type, Consumer<T> resultConsumer, Function<SearchResults, Optional<T>> extractResult) {
|
||||
public static <T extends Searchable> Optional<MastodonAPIRequest<SearchResults>> lookup(Context context, T query, String targetAccountID, @Nullable String sourceAccountID, @Nullable GetSearchResults.Type type, Consumer<T> resultConsumer, Function<SearchResults, Optional<T>> extractResult) {
|
||||
if (sourceAccountID != null && targetAccountID.startsWith(sourceAccountID.substring(0, sourceAccountID.indexOf('_')))) {
|
||||
resultConsumer.accept(query);
|
||||
return;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
Optional<T> result = extractResult.apply(results);
|
||||
if (result.isPresent()) resultConsumer.accept(result.get());
|
||||
else {
|
||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
resultConsumer.accept(null);
|
||||
}
|
||||
}
|
||||
return Optional.of(new GetSearchResults(query.getQuery(), type, true).setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
Optional<T> result = extractResult.apply(results);
|
||||
if (result.isPresent()) resultConsumer.accept(result.get());
|
||||
else {
|
||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
resultConsumer.accept(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
}
|
||||
})
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, targetAccountID, null, d))
|
||||
.exec(targetAccountID);
|
||||
.exec(targetAccountID));
|
||||
}
|
||||
|
||||
public static void openURL(Context context, String accountID, String url) {
|
||||
openURL(context, accountID, url, true);
|
||||
}
|
||||
|
||||
private static void transformDialogForLookup(Context context, String accountID, @Nullable String url, ProgressDialog dialog) {
|
||||
public static void transformDialogForLookup(Context context, String accountID, @Nullable String url, ProgressDialog dialog) {
|
||||
if (accountID != null) {
|
||||
dialog.setTitle(context.getString(R.string.sk_loading_resource_on_instance_title, getInstanceName(accountID)));
|
||||
} else {
|
||||
@@ -1110,11 +1107,35 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private static Bundle bundleError(String error) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("error", error);
|
||||
return args;
|
||||
}
|
||||
|
||||
private static Bundle bundleError(ErrorResponse error) {
|
||||
Bundle args = new Bundle();
|
||||
if (error instanceof MastodonErrorResponse e) {
|
||||
args.putString("error", e.error);
|
||||
args.putInt("httpStatus", e.httpStatus);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public static void openURL(Context context, String accountID, String url) {
|
||||
openURL(context, accountID, url, true);
|
||||
}
|
||||
|
||||
public static void openURL(Context context, String accountID, String url, boolean launchBrowser) {
|
||||
lookupURL(context, accountID, url, launchBrowser, (clazz, args) -> {
|
||||
if (clazz == null) return;
|
||||
lookupURL(context, accountID, url, (clazz, args) -> {
|
||||
if (clazz == null) {
|
||||
if (args != null && args.containsKey("error")) Toast.makeText(context, args.getString("error"), Toast.LENGTH_SHORT).show();
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
return;
|
||||
}
|
||||
Nav.go((Activity) context, clazz, args);
|
||||
});
|
||||
}).map(req -> req.wrapProgress((Activity) context, R.string.loading, true, d ->
|
||||
transformDialogForLookup(context, accountID, url, d)));
|
||||
}
|
||||
|
||||
public static boolean acctMatches(String accountID, String acct, String queriedUsername, @Nullable String queriedDomain) {
|
||||
@@ -1137,15 +1158,17 @@ public class UiUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void lookupAccountHandle(Context context, String accountID, String query, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
parseFediverseHandle(query).ifPresentOrElse(
|
||||
handle -> lookupAccountHandle(context, accountID, handle, go),
|
||||
() -> go.accept(null, null)
|
||||
);
|
||||
public static Optional<MastodonAPIRequest<SearchResults>> lookupAccountHandle(Context context, String accountID, String query, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
return parseFediverseHandle(query).map(
|
||||
handle -> lookupAccountHandle(context, accountID, handle, go))
|
||||
.or(() -> {
|
||||
go.accept(null, null);
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
public static void lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
public static MastodonAPIRequest<SearchResults> lookupAccountHandle(Context context, String accountID, Pair<String, Optional<String>> queryHandle, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
String fullHandle = ("@" + queryHandle.first) + (queryHandle.second.map(domain -> "@" + domain).orElse(""));
|
||||
new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
|
||||
return new GetSearchResults(fullHandle, GetSearchResults.Type.ACCOUNTS, true)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
@@ -1159,29 +1182,22 @@ public class UiUtils {
|
||||
go.accept(ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
args.putString("error", context.getString(R.string.sk_resource_not_found));
|
||||
go.accept(null, null);
|
||||
go.accept(null, bundleError(context.getString(R.string.sk_resource_not_found)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
Bundle args = new Bundle();
|
||||
if (error instanceof MastodonErrorResponse e) {
|
||||
args.putString("error", e.error);
|
||||
args.putInt("httpStatus", e.httpStatus);
|
||||
}
|
||||
go.accept(null, args);
|
||||
go.accept(null, bundleError(error));
|
||||
}
|
||||
}).exec(accountID);
|
||||
}
|
||||
|
||||
public static void lookupURL(Context context, String accountID, String url, boolean launchBrowser, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
public static Optional<MastodonAPIRequest<?>> lookupURL(Context context, String accountID, String url, BiConsumer<Class<? extends Fragment>, Bundle> go) {
|
||||
Uri uri = Uri.parse(url);
|
||||
List<String> path = uri.getPathSegments();
|
||||
if (accountID != null && "https".equals(uri.getScheme())) {
|
||||
if (path.size() == 2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())) {
|
||||
new GetStatusByID(path.get(1))
|
||||
return Optional.of(new GetStatusByID(path.get(1))
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(Status result) {
|
||||
@@ -1193,17 +1209,12 @@ public class UiUtils {
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
go.accept(null, bundleError(error));
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, accountID, url, d))
|
||||
.exec(accountID);
|
||||
return;
|
||||
.exec(accountID));
|
||||
} else if (looksLikeFediverseUrl(url)) {
|
||||
new GetSearchResults(url, null, true)
|
||||
return Optional.of(new GetSearchResults(url, null, true)
|
||||
.setCallback(new Callback<>() {
|
||||
@Override
|
||||
public void onSuccess(SearchResults results) {
|
||||
@@ -1221,26 +1232,19 @@ public class UiUtils {
|
||||
go.accept(ProfileFragment.class, args);
|
||||
return;
|
||||
}
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
Toast.makeText(context, R.string.sk_resource_not_found, Toast.LENGTH_SHORT).show();
|
||||
go.accept(null, null);
|
||||
go.accept(null, bundleError(context.getString(R.string.sk_resource_not_found)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorResponse error) {
|
||||
error.showToast(context);
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
go.accept(null, bundleError(error));
|
||||
}
|
||||
})
|
||||
.wrapProgress((Activity) context, R.string.loading, true,
|
||||
d -> transformDialogForLookup(context, accountID, url, d))
|
||||
.exec(accountID);
|
||||
return;
|
||||
.exec(accountID));
|
||||
}
|
||||
}
|
||||
if (launchBrowser) launchWebBrowser(context, url);
|
||||
go.accept(null, null);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static void copyText(View v, String text) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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="M3.28 2.22c-0.293-0.293-0.767-0.293-1.06 0-0.293 0.293-0.293 0.767 0 1.06l4.804 4.805-3.867 0.561C2.05 8.807 1.607 10.168 2.41 10.95l3.815 3.719-0.9 5.251c-0.19 1.103 0.968 1.944 1.958 1.423l4.716-2.479 4.716 2.48c0.99 0.52 2.148-0.32 1.96-1.424l-0.04-0.223 2.085 2.084c0.293 0.293 0.768 0.293 1.061 0 0.293-0.292 0.293-0.767 0-1.06L3.28 2.22zm13.518 15.639l0.345 2.014-4.516-2.374c-0.394-0.207-0.864-0.207-1.257 0l-4.516 2.374 0.862-5.03c0.075-0.437-0.07-0.884-0.388-1.194l-3.654-3.562 4.673-0.679 8.45 8.45zm3.525-7.772l-3.572 3.482 1.06 1.06 3.777-3.68c0.8-0.781 0.359-2.142-0.748-2.303L15.567 7.88l-2.358-4.777c-0.495-1.004-1.926-1.004-2.421 0L9.3 6.117l1.12 1.12 1.578-3.2 2.259 4.577c0.196 0.398 0.577 0.674 1.016 0.738l5.05 0.734z" android:fillColor="@color/fluent_default_icon_tint"/>
|
||||
</vector>
|
||||
12
mastodon/src/main/res/menu/settings_auto_reveal_spoiler.xml
Normal file
12
mastodon/src/main/res/menu/settings_auto_reveal_spoiler.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/auto_reveal_never"
|
||||
android:title="@string/sk_settings_auto_reveal_nobody" />
|
||||
<item
|
||||
android:id="@+id/auto_reveal_threads"
|
||||
android:title="@string/sk_settings_auto_reveal_author" />
|
||||
<item
|
||||
android:id="@+id/auto_reveal_discussions"
|
||||
android:title="@string/sk_settings_auto_reveal_anyone" />
|
||||
</menu>
|
||||
@@ -294,6 +294,10 @@
|
||||
<string name="sk_settings_content_types_explanation">Dadurch lässt beim Erstellen von Beiträgen ein Inhaltstyp wie Markdown angeben. Nicht alle Instanzen unterstützen das.</string>
|
||||
<string name="sk_settings_allow_remote_loading">Infos von Remote-Instanzen laden</string>
|
||||
<string name="sk_no_remote_info_hint">keine Remote-Infos abrufbar</string>
|
||||
<string name="sk_error_loading_profile">Konnte das Profil auf deiner Heim-Instanz nicht laden.</string>
|
||||
<string name="sk_error_loading_profile">Konnte das Profil via %s nicht laden</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Für vollständigere Auflistung von Follower*innen, Likes und Boosts können die Informationen von der Ursprungs-Instanz geladen werden.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Zeigen von gleichen CWs in Antworten von</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">niemandem</string>
|
||||
<string name="sk_settings_auto_reveal_author">Autor*in</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">allen</string>
|
||||
</resources>
|
||||
@@ -286,10 +286,19 @@
|
||||
<string name="sk_settings_default_content_type">Contenido por defecto</string>
|
||||
<string name="sk_settings_content_types_explanation">Permite establecer un tipo de contenido como Markdown al crear una entrada. Ten en cuenta que no todas las instancias lo admiten.</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Permite preseleccionar un tipo de contenido al crear nuevas entradas, anulando el valor establecido en \"Preferencias de publicación\".</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Estas son las publicaciones más recientes de la gente en tu servidor de Akkoma.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Estas son las publicaciones más recientes de la red seleccionadas por los administradores de tu instancia.</string>
|
||||
<string name="sk_timeline_bubble">Burbuja</string>
|
||||
<string name="sk_instance_info_unavailable">Información de la instancia temporalmente no disponible</string>
|
||||
<string name="sk_external_share_or_open_title">Compartir o abrir con una cuenta</string>
|
||||
<string name="sk_open_in_app">Abrir en la app</string>
|
||||
<string name="sk_external_share_title">Compartir con una cuenta</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Mostrar CW iguales en las respuestas de</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">nadie</string>
|
||||
<string name="sk_settings_auto_reveal_author">autor</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">todos</string>
|
||||
<string name="sk_open_in_app_failed">No se pudo abrir en la aplicación</string>
|
||||
<string name="sk_no_remote_info_hint">no hay información remota disponible</string>
|
||||
<string name="sk_error_loading_profile">No se pudo cargar el perfil a través de %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Cargar la información desde las instancias remotas</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Intenta obtener listas más precisas de seguidores, Me gusta y promociones cargando la información desde la instancia de origen.</string>
|
||||
</resources>
|
||||
@@ -294,4 +294,8 @@
|
||||
<string name="sk_timeline_bubble">Bulle</string>
|
||||
<string name="sk_instance_info_unavailable">Informations sur l\'instance temporairement indisponibles</string>
|
||||
<string name="sk_open_in_app_failed">Impossible de l\'ouvrir dans l\'application</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_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>
|
||||
</resources>
|
||||
@@ -289,8 +289,13 @@
|
||||
<string name="sk_settings_content_types_explanation">Memperbolehkan menetapkan jenis konten seperti Markdown ketika membuat kiriman. Perlu diingat bahwa tidak semua server mendukung ini.</string>
|
||||
<string name="sk_open_in_app">Buka dalam aplikasi</string>
|
||||
<string name="sk_external_share_title">Bagikan dengan akun</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yamg paling terkini oleh orang-orang dalam gelembung server Akkoma Anda.</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Ini adalah kiriman yang paling terkini dari jaringan dikurasikan oleh admin server Anda.</string>
|
||||
<string name="sk_timeline_bubble">Gelembung</string>
|
||||
<string name="sk_instance_info_unavailable">Info server sementara tidak tersedia</string>
|
||||
<string name="sk_external_share_or_open_title">Bagikan atau buka dengan akun</string>
|
||||
<string name="sk_no_remote_info_hint">info jarak jauh tidak tersedia</string>
|
||||
<string name="sk_settings_allow_remote_loading">Muat info dari server jarak jauh</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Coba mendapatkan pendaftaran akurat untuk pengikut cr</string>
|
||||
<string name="sk_error_loading_profile">Gagal memuat profil melalui %s</string>
|
||||
<string name="sk_open_in_app_failed">Tidak dapat buka dalam aplikasi</string>
|
||||
</resources>
|
||||
@@ -274,4 +274,26 @@
|
||||
<string name="sk_compact_reblog_reply_line">Linea boost/risposta compatta</string>
|
||||
<string name="sk_reacted_with">ha reagito con %s</string>
|
||||
<string name="sk_reply_line_above_avatar">Linea \"In risposta a\" sopra l\'avatar</string>
|
||||
<string name="sk_bubble_timeline_info_banner">Questi sono i post più recenti della rete, curati dagli amministratori della tua istanza.</string>
|
||||
<string name="sk_settings_content_types_explanation">Permette di impostare un tipo di contenuto, come Markdown, quando si crea un post. Tieni presente che non tutte le istanze lo supportano.</string>
|
||||
<string name="sk_open_in_app_failed">Impossibile aprire nell\'app</string>
|
||||
<string name="sk_external_share_title">Condividi con l\'account</string>
|
||||
<string name="sk_external_share_or_open_title">Condividi o apri con l\'account</string>
|
||||
<string name="sk_no_remote_info_hint">Informazioni remote non disponibili</string>
|
||||
<string name="sk_error_loading_profile">Impossibile caricare il profilo tramite %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Carica informazioni da istanze remote</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Cerca di ottenere elenchi più accurati di follower, like e boost caricando le informazioni dall\'istanza di origine.</string>
|
||||
<string name="sk_content_type">Tipo di contenuto</string>
|
||||
<string name="sk_timeline_bubble">Bolla</string>
|
||||
<string name="sk_content_type_unspecified">Non specificato</string>
|
||||
<string name="sk_content_type_plain">Testo semplice</string>
|
||||
<string name="sk_content_type_html">HTML</string>
|
||||
<string name="sk_content_type_markdown">Markdown</string>
|
||||
<string name="sk_content_type_bbcode">BBCode</string>
|
||||
<string name="sk_content_type_mfm">MFM</string>
|
||||
<string name="sk_settings_content_types">Abilita la formattazione dei post</string>
|
||||
<string name="sk_settings_default_content_type">Tipo di contenuto predefinito</string>
|
||||
<string name="sk_settings_default_content_type_explanation">Ti permette di preselezionare un tipo di contenuto quando si creano nuovi post, sovrascrivendo il valore impostato in \"Preferenze di pubblicazione\".</string>
|
||||
<string name="sk_instance_info_unavailable">Informazioni sull\'istanza temporaneamente non disponibili</string>
|
||||
<string name="sk_open_in_app">Apri nell\'app</string>
|
||||
</resources>
|
||||
@@ -269,6 +269,7 @@
|
||||
<string name="hide_content">Dölj innehåll</string>
|
||||
<string name="new_post">Nytt inlägg</string>
|
||||
<string name="button_reply">Svara</string>
|
||||
<string name="button_reblog">Boosta</string>
|
||||
<string name="button_favorite">Favoritmarkera</string>
|
||||
<string name="button_share">Dela</string>
|
||||
<string name="media_no_description">Media utan beskrivning</string>
|
||||
@@ -428,5 +429,7 @@
|
||||
<string name="signup_or_login">eller</string>
|
||||
<string name="learn_more">Läs mer</string>
|
||||
<string name="welcome_to_mastodon">Välkommen till Mastodon</string>
|
||||
<string name="welcome_paragraph1">Mastodon är ett decentraliserat socialt nätverk, vilket innebär att inget enskilt företag kontrollerar det. Det består av många oberoende servrar, alla sammankopplade.</string>
|
||||
<string name="what_are_servers">Vad är servrar?</string>
|
||||
<string name="welcome_paragraph2"><![CDATA[Varje Mastodon-konto finns på en server — var och en med sina värderingar, regler och administratörer. Oavsett vilken du väljer kan du följa och interagera med människor på vilken server som helst.]]></string>
|
||||
</resources>
|
||||
|
||||
@@ -293,4 +293,8 @@
|
||||
<string name="sk_instance_info_unavailable">Сервер тимчасово недоступний</string>
|
||||
<string name="sk_external_share_or_open_title">Поділитися або відкрити за допомогою облікового запису</string>
|
||||
<string name="sk_open_in_app_failed">Не вдалося відкрити в застосунку</string>
|
||||
<string name="sk_no_remote_info_hint">віддалена інформація недоступна</string>
|
||||
<string name="sk_settings_allow_remote_loading">Завантажити інформацію з віддалених серверів</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Спробуйте отримати точніші списки підписників, вподобань і поширень, завантаживши інформацію з джерела.</string>
|
||||
<string name="sk_error_loading_profile">Не вдалося завантажити профіль на ваш домашній сервер.</string>
|
||||
</resources>
|
||||
@@ -294,7 +294,11 @@
|
||||
<string name="sk_external_share_title">Share with account</string>
|
||||
<string name="sk_external_share_or_open_title">Share or open with account</string>
|
||||
<string name="sk_no_remote_info_hint">remote info unavailable</string>
|
||||
<string name="sk_error_loading_profile">Failed loading the profile on your home instance.</string>
|
||||
<string name="sk_error_loading_profile">Failed loading the profile via %s</string>
|
||||
<string name="sk_settings_allow_remote_loading">Load info from remote instances</string>
|
||||
<string name="sk_settings_allow_remote_loading_explanation">Try fetching more accurate listings for followers, likes and boosts by loading the information from the instance of origin.</string>
|
||||
<string name="sk_settings_auto_reveal_equal_spoilers">Reveal same CWs in replies from</string>
|
||||
<string name="sk_settings_auto_reveal_nobody">nobody</string>
|
||||
<string name="sk_settings_auto_reveal_author">author</string>
|
||||
<string name="sk_settings_auto_reveal_anyone">everyone</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user