Merge branch 'develop'

maths 3.14.0
Thomas 2 years ago
commit 8c98b9e46d

@ -13,8 +13,8 @@ android {
defaultConfig {
minSdk 21
targetSdk 33
versionCode 457
versionName "3.13.3"
versionCode 462
versionName "3.14.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
flavorDimensions "default"

@ -1,4 +1,29 @@
[
{
"version": "3.14.0",
"code": "462",
"note": "Added:\n\n- Add Bubble timeline support in extra-features with filters\n- Allow to display public profiles by default to get all messages (Settings > Interface)\n- Glitch: Allow to post messages locally (Can be turned off in Settings)\n- Pixelfed: Custom layout to display Media fully (Also works for other software when there are media)\n- Allow to align left action buttons in messages\n\nChanged:\n- Full rework on links in messages (also mentions and tags)\n- Add pinned tag in \"any\" to avoid to lose it when renaming timeline\n\nFixed:\n- Links to messages not handled by the app\n- CW when editing a message\n- Fix push notifications with several accounts\n- New messages or edition notifications not pushed\n- Fix quotes with tags/mentions\n- Fix notifications\n- Fix sending multiple media\n- Fix crashes"
},
{
"version": "3.13.7",
"code": "461",
"note": "Added:\n- Pixelfed: Custom layout to display Media fully \n*(Settings > Timelines > Pixelfed Presentation) - Also works for other softwares when there are media\n\nChanged:\n- Add pinned tag in \"any\" to avoid to lose it when renaming timeline\n\nFixed:\n- Fix push notifications with several accounts\n- Fix quotes with tags/mentions\n- Fix notifications\n- Fix sending multiple media\n- Some crashes"
},
{
"version": "3.13.6",
"code": "460",
"note": "Fixed:\n- Cross-compose: Wrong instance emojis\n- Custom emojis not displayed in notifications\n- Fav/Boost markers with shared messages\n- Empty notifications\n- Fix cw removed when replying\n- Fix expand media with fit preview images when sensitive\n- Fix an issue with fetch more displayed too often (cache clear will help or wait new messages)"
},
{
"version": "3.13.5",
"code": "459",
"note": "Added:\n- Glitch: Allow to post messages locally (Can be turned off in Settings)\n\nFixed:\n- Crashes"
},
{
"version": "3.13.4",
"code": "458",
"note": "Added:\n- Add Bubble timeline support in extra-features with filters\n- Allow to display public profiles by default to get all messages (Settings > Interface)\n\nChanged:\n- Full rework on links in messages (also mentions and tags)\n\nFixed:\n- Spoiler text when editing\n- Fix watermarks"
},
{
"version": "3.13.3",
"code": "457",

@ -165,6 +165,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
public abstract class BaseMainActivity extends BaseActivity implements NetworkStateReceiver.NetworkStateReceiverListener, FragmentMastodonTimeline.UpdateCounters, FragmentNotificationContainer.UpdateCounters, FragmentMastodonConversation.UpdateCounters {
private static final int REQUEST_CODE = 5415;
public static String currentInstance, currentToken, currentUserID, client_id, client_secret, software;
public static HashMap<String, List<Emoji>> emojis = new HashMap<>();
public static Account.API api;
@ -297,7 +298,6 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
};
private NetworkStateReceiver networkStateReceiver;
private boolean headerMenuOpen;
private static final int REQUEST_CODE = 5415;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -683,6 +683,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
regex_local = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_LOCAL) + currentUserID + currentInstance, null);
regex_public = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_PUBLIC) + currentUserID + currentInstance, null);
show_art_nsfw = sharedpreferences.getBoolean(getString(R.string.SET_ART_WITH_NSFW) + currentUserID + currentInstance, false);
binding.profilePicture.setOnClickListener(v -> binding.drawerLayout.openDrawer(GravityCompat.START));
Helper.loadPP(BaseMainActivity.this, binding.profilePicture, currentAccount);
headerMainBinding.accountAcc.setText(String.format("%s@%s", currentAccount.mastodon_account.username, currentAccount.instance));
@ -785,7 +786,6 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
});
binding.toolbarSearch.setOnSearchClickListener(v -> binding.tabLayout.setVisibility(View.VISIBLE));
//For receiving data from other activities
LocalBroadcastManager.getInstance(BaseMainActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA));
@ -1041,7 +1041,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
String title = "";
String description = "";
if(titleEl != null) {
if (titleEl != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
title = Html.fromHtml(titleEl.attr("content"), Html.FROM_HTML_MODE_LEGACY).toString();
} else {
@ -1049,7 +1049,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
}
}
if(descriptionEl != null) {
if (descriptionEl != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
description = Html.fromHtml(descriptionEl.attr("content"), Html.FROM_HTML_MODE_LEGACY).toString();
} else {
@ -1058,13 +1058,13 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
}
String imageUrl = "";
if(imageUrlEl != null) {
if (imageUrlEl != null) {
imageUrl = imageUrlEl.attr("content");
}
StringBuilder titleBuilder = new StringBuilder();
if(!originalUrl.trim().equalsIgnoreCase(sharedText.trim())) {
if (!originalUrl.trim().equalsIgnoreCase(sharedText.trim())) {
// If the shared text is not just the URL, add it to the top
String toAppend = sharedText.replaceAll("\\s*" + Pattern.quote(originalUrl) + "\\s*", "");
titleBuilder.append(toAppend);
@ -1072,7 +1072,8 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
if (title.length() > 0) {
// OG title fetched from source
if(titleBuilder.length() > 0) titleBuilder.append("\n\n");
if (titleBuilder.length() > 0)
titleBuilder.append("\n\n");
titleBuilder.append(title);
}
@ -1138,7 +1139,6 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
b.putSerializable(Helper.ARG_MEDIA_ATTACHMENTS, new ArrayList<>(attachments));
CrossActionHelper.doCrossShare(BaseMainActivity.this, b);
});
CrossActionHelper.doCrossShare(BaseMainActivity.this, b);
} else {
Toasty.warning(BaseMainActivity.this, getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}

@ -320,6 +320,7 @@ public class AccountReportActivity extends BaseBarActivity {
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {

@ -107,11 +107,6 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
private Status statusReply, statusMention, statusQuoted;
private StatusDraft statusDraft;
private ComposeAdapter composeAdapter;
private boolean promptSaveDraft;
private boolean restoredDraft;
private List<Attachment> sharedAttachments;
private final BroadcastReceiver imageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(android.content.Context context, Intent intent) {
@ -138,7 +133,9 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
}
};
private boolean promptSaveDraft;
private boolean restoredDraft;
private List<Attachment> sharedAttachments;
private ActivityPaginationBinding binding;
private BaseAccount account;
private String instance, token;
@ -537,10 +534,10 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
if (token == null) {
token = account.token;
}
if (emojis == null || !emojis.containsKey(currentInstance)) {
if (emojis == null || !emojis.containsKey(instance)) {
new Thread(() -> {
try {
emojis.put(currentInstance, new EmojiInstance(ComposeActivity.this).getEmojiList(currentInstance));
emojis.put(instance, new EmojiInstance(ComposeActivity.this).getEmojiList(instance));
} catch (DBException e) {
e.printStackTrace();
}
@ -637,6 +634,9 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
if (statusReply.spoiler_text != null) {
statusDraftList.get(0).spoiler_text = statusReply.spoiler_text;
if (statusReply.spoiler_text.trim().length() > 0) {
statusDraftList.get(0).spoilerChecked = true;
}
}
if (statusReply.language != null && !statusReply.language.isEmpty()) {
statusDraftList.get(0).language = statusReply.language;

@ -221,6 +221,8 @@ public class HashTagActivity extends BaseActivity {
tagTimeline.name = stripTag.trim();
tagTimeline.isNSFW = false;
tagTimeline.isART = false;
tagTimeline.any = new ArrayList<>();
tagTimeline.any.add(stripTag.trim());
pinnedTimeline.tagTimeline = tagTimeline;
pinned.pinnedTimelines.add(pinnedTimeline);
if (update) {

@ -117,8 +117,6 @@ public class ProfileActivity extends BaseActivity {
private String mention_str;
private WellKnownNodeinfo.NodeInfo nodeInfo;
private boolean checkRemotely;
private boolean homeMuted;
private final BroadcastReceiver broadcast_data = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -133,6 +131,7 @@ public class ProfileActivity extends BaseActivity {
}
}
};
private boolean homeMuted;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -145,6 +144,7 @@ public class ProfileActivity extends BaseActivity {
Bundle b = getIntent().getExtras();
binding.accountFollow.setEnabled(false);
checkRemotely = false;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
homeMuted = false;
if (b != null) {
account = (Account) b.getSerializable(Helper.ARG_ACCOUNT);
@ -152,6 +152,9 @@ public class ProfileActivity extends BaseActivity {
mention_str = b.getString(Helper.ARG_MENTION, null);
checkRemotely = b.getBoolean(Helper.ARG_CHECK_REMOTELY, false);
}
if (!checkRemotely) {
checkRemotely = sharedpreferences.getBoolean(getString(R.string.SET_PROFILE_REMOTELY), false);
}
ActivityCompat.postponeEnterTransition(ProfileActivity.this);
//Remove title
if (actionBar != null) {
@ -162,7 +165,7 @@ public class ProfileActivity extends BaseActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
float scale = sharedpreferences.getFloat(getString(R.string.SET_FONT_SCALE), 1.1f);
binding.title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18 * 1.1f / scale);
accountsVM = new ViewModelProvider(ProfileActivity.this).get(AccountsVM.class);

@ -71,7 +71,12 @@ public interface MastodonNotificationsService {
@Field("data[alerts][favourite]") boolean favourite,
@Field("data[alerts][reblog]") boolean reblog,
@Field("data[alerts][mention]") boolean mention,
@Field("data[alerts][poll]") boolean poll
@Field("data[alerts][poll]") boolean poll,
@Field("data[alerts][status]") boolean status,
@Field("data[alerts][update]") boolean update,
@Field("data[alerts][admin.sign_up]") boolean admin_sign_up,
@Field("data[alerts][admin.report]") boolean admin_report
);
@GET("push/subscription")

@ -53,6 +53,19 @@ public interface MastodonTimelinesService {
@Query("limit") Integer limit
);
@GET("timelines/bubble")
Call<List<Status>> getBubble(
@Header("Authorization") String token,
@Query("only_media") Boolean only_media,
@Query("remote") Boolean remote,
@Query("with_muted") Boolean with_muted,
@Query("exclude_visibilities") List<String> exclude_visibilities,
@Query("reply_visibility") String reply_visibility,
@Query("max_id") String max_id,
@Query("since_id") String since_id,
@Query("min_id") String min_id,
@Query("limit") Integer limit
);
@GET("trends/statuses")
Call<List<Status>> getStatusTrends(

@ -87,35 +87,13 @@ public class Account implements Serializable {
public Account moved;
@SerializedName("role")
public Role role;
public static class Role implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("color")
public String color;
@SerializedName("position")
public int position;
@SerializedName("permissions")
public int permissions;
@SerializedName("highlighted")
public boolean highlighted;
@SerializedName("created_at")
public Date created_at;
@SerializedName("updated_at")
public Date updated_at;
}
public transient RelationShip relationShip;
public synchronized Spannable getSpanDisplayName(Context context, WeakReference<View> viewWeakReference) {
if (display_name == null || display_name.isEmpty()) {
display_name = username;
}
return SpannableHelper.convert(context, display_name, null, this, null, false, false, viewWeakReference);
return SpannableHelper.convert(context, display_name, null, this, null, viewWeakReference);
}
public synchronized Spannable getSpanDisplayName(Activity activity, WeakReference<View> viewWeakReference) {
@ -126,11 +104,39 @@ public class Account implements Serializable {
}
public synchronized Spannable getSpanDisplayNameTitle(Context context, WeakReference<View> viewWeakReference, String title) {
return SpannableHelper.convert(context, title, null, this, null, false, false, viewWeakReference);
return SpannableHelper.convert(context, title, null, this, null, viewWeakReference);
}
public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, note, null, this, null, true, true, viewWeakReference);
return SpannableHelper.convert(context, note, null, this, null, viewWeakReference);
}
@Override
public boolean equals(@Nullable Object obj) {
boolean same = false;
if (obj instanceof Account) {
same = this.id.equals(((Account) obj).id);
}
return same;
}
public static class Role implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("color")
public String color;
@SerializedName("position")
public int position;
@SerializedName("permissions")
public int permissions;
@SerializedName("highlighted")
public boolean highlighted;
@SerializedName("created_at")
public Date created_at;
@SerializedName("updated_at")
public Date updated_at;
}
public static class AccountParams implements Serializable {
@ -150,14 +156,4 @@ public class Account implements Serializable {
public LinkedHashMap<Integer, Field.FieldParams> fields;
}
@Override
public boolean equals(@Nullable Object obj) {
boolean same = false;
if (obj instanceof Account) {
same = this.id.equals(((Account) obj).id);
}
return same;
}
}

@ -56,7 +56,7 @@ public class Announcement {
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, content, null, null, this, true, false, viewWeakReference);
return SpannableHelper.convert(context, content, null, null, this, viewWeakReference);
}
}

@ -47,7 +47,7 @@ public class Field implements Serializable {
if (verified_at != null && value != null) {
value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text));
}
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, true, true, viewWeakReference);
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, viewWeakReference);
if (value_span != null && spannable != null) {
spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
@ -57,7 +57,7 @@ public class Field implements Serializable {
public synchronized Spannable getLabelSpan(Context context, Account account, WeakReference<View> viewWeakReference) {
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, true, true, viewWeakReference);
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, viewWeakReference);
if (name_span != null && spannable != null) {
spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

@ -61,7 +61,7 @@ public class Poll implements Serializable {
public transient Spannable span_title;
public Spannable getSpanTitle(Context context, Status status, WeakReference<View> viewWeakReference) {
span_title = SpannableHelper.convert(context, title, status, null, null, false, false, viewWeakReference);
span_title = SpannableHelper.convert(context, title, status, null, null, viewWeakReference);
return span_title;
}
}

@ -102,13 +102,15 @@ public class Status implements Serializable, Cloneable {
public List<Filter.FilterResult> filtered;
@SerializedName("pleroma")
public Pleroma pleroma;
@SerializedName("local_only")
public boolean local_only = false;
@SerializedName("cached")
public boolean cached = false;
public Attachment art_attachment;
public boolean isExpended = false;
public boolean isTruncated = true;
public boolean isFetchMore = false;
public PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;
public transient boolean isFetchMore = false;
public transient PositionFetchMore positionFetchMore = PositionFetchMore.BOTTOM;
public boolean isChecked = false;
public String translationContent;
public boolean translationShown;
@ -134,29 +136,25 @@ public class Status implements Serializable, Cloneable {
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpan == null) {
contentSpan = SpannableHelper.convert(context, content, this, null, null, true, false, viewWeakReference, callback);
contentSpan = SpannableHelper.convert(context, content, this, null, null, viewWeakReference, callback);
}
return contentSpan;
}
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpoilerSpan == null) {
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, true, false, viewWeakReference, callback);
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, viewWeakReference, callback);
}
return contentSpoilerSpan;
}
public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentTranslateSpan == null) {
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, true, false, viewWeakReference, callback);
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, viewWeakReference, callback);
}
return contentTranslateSpan;
}
public interface Callback {
void emojiFetched();
}
@NonNull
public Object clone() throws CloneNotSupportedException {
return super.clone();
@ -167,4 +165,8 @@ public class Status implements Serializable, Cloneable {
BOTTOM
}
public interface Callback {
void emojiFetched();
}
}

@ -25,16 +25,6 @@ import app.fedilab.android.client.entities.api.Account;
public class AdminAccount implements Serializable {
@SerializedName("id")
public String id;
@SerializedName("username")
public String username;
@SerializedName("domain")
public String domain;
@SerializedName("created_at")
public Date created_at;
@SerializedName("email")
public String email;
public static LinkedHashMap<Integer, String> permissions;
static {
@ -61,6 +51,16 @@ public class AdminAccount implements Serializable {
permissions.put(80000, "Delete User Data");
}
@SerializedName("id")
public String id;
@SerializedName("username")
public String username;
@SerializedName("domain")
public String domain;
@SerializedName("created_at")
public Date created_at;
@SerializedName("email")
public String email;
@SerializedName("ip")
public String ip;
@SerializedName("role")

@ -0,0 +1,35 @@
package app.fedilab.android.client.entities.app;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.util.List;
public class BubbleTimeline implements Serializable {
@SerializedName("id")
public int id;
@SerializedName("only_media")
public boolean only_media = false;
@SerializedName("remote")
public boolean remote = false;
@SerializedName("with_muted")
public boolean with_muted;
@SerializedName("exclude_visibilities")
public List<String> exclude_visibilities = null;
@SerializedName("reply_visibility")
public String reply_visibility = null;
}

@ -38,6 +38,8 @@ public class PinnedTimeline implements Serializable {
public RemoteInstance remoteInstance;
@SerializedName("tagTimeline")
public TagTimeline tagTimeline;
@SerializedName("bubbleTimeline")
public BubbleTimeline bubbleTimeline;
@SerializedName("mastodonList")
public MastodonList mastodonList;
@SerializedName("currentFilter")

@ -509,7 +509,7 @@ public class StatusCache {
try {
db.delete(Sqlite.TABLE_STATUS_CACHE,
Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =? AND " + Sqlite.COL_STATUS + " LIKE ?",
new String[]{userid, instance, "%\"id\":\"" + targetedUser + "\"%" });
new String[]{userid, instance, "%\"id\":\"" + targetedUser + "\"%"});
} catch (Exception e) {
e.printStackTrace();
}

@ -364,6 +364,8 @@ public class Timeline {
LOCAL("LOCAL"),
@SerializedName("PUBLIC")
PUBLIC("PUBLIC"),
@SerializedName("BUBBLE")
BUBBLE("BUBBLE"),
@SerializedName("CONTEXT")
CONTEXT("CONTEXT"),
@SerializedName("TAG")

@ -0,0 +1,237 @@
package app.fedilab.android.helper;
/* Copyright 2022 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.client.entities.app.StatusCache.restoreNotificationFromString;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import androidx.preference.PreferenceManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import app.fedilab.android.client.entities.api.Notification;
public class ECDHFedilab {
public static final String kp_public = "kp_public";
public static final String peer_public = "peer_public";
public static final String name = "prime256v1";
private static final byte[] P256_HEAD = new byte[]{(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
(byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, (byte) 0x08, (byte) 0x2a, (byte) 0x86,
(byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03, (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00};
static {
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}
private final KeyPairGenerator kpg;
private final PublicKey publicKey;
private final String encodedPublicKey;
private final byte[] authKey;
private final String slug;
private final String pushPublicKey;
private final String encodedAuthKey;
private final String pushAccountID;
private final String pushPrivateKey;
PrivateKey privateKey;
private String pushPrivateKe;
public ECDHFedilab(Context context, String slug) throws Exception {
if (slug == null) {
throw new Exception("slug cannot be null");
}
try {
kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec spec = new ECGenParameterSpec("prime256v1");
kpg.initialize(spec);
KeyPair keyPair = kpg.generateKeyPair();
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
encodedPublicKey = Base64.encodeToString(serializeRawPublicKey(publicKey), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
authKey = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(authKey);
byte[] randomAccountID = new byte[16];
secureRandom.nextBytes(randomAccountID);
pushPrivateKey = Base64.encodeToString(privateKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
pushPublicKey = Base64.encodeToString(publicKey.getEncoded(), Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
encodedAuthKey = Base64.encodeToString(authKey, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
pushAccountID = Base64.encodeToString(randomAccountID, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
prefsEditor.putString("pushPrivateKey" + slug, pushPrivateKey);
prefsEditor.putString("pushPublicKey" + slug, pushPublicKey);
prefsEditor.putString("encodedAuthKey" + slug, encodedAuthKey);
prefsEditor.putString("pushAccountID" + slug, pushAccountID);
prefsEditor.apply();
this.slug = slug;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static String getServerKey(Context context, String slug) {
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);
return sharedPreferences.getString("server_key" + slug, null);
}
private static byte[] serializeRawPublicKey(PublicKey key) {
ECPoint point = ((ECPublicKey) key).getW();
byte[] x = point.getAffineX().toByteArray();
byte[] y = point.getAffineY().toByteArray();
if (x.length > 32)
x = Arrays.copyOfRange(x, x.length - 32, x.length);
if (y.length > 32)
y = Arrays.copyOfRange(y, y.length - 32, y.length);
byte[] result = new byte[65];
result[0] = 4;
System.arraycopy(x, 0, result, 1 + (32 - x.length), x.length);
System.arraycopy(y, 0, result, result.length - y.length, y.length);
return result;
}
public static Notification decryptNotification(Context context, String slug, byte[] messageEncrypted) {
SharedPreferences sharedPreferences = PreferenceManager
.getDefaultSharedPreferences(context);
String pushPrivateKey = sharedPreferences.getString("pushPrivateKey" + slug, null);
String pushPublicKey = sharedPreferences.getString("pushPublicKey" + slug, null);
String encodedAuthKey = sharedPreferences.getString("encodedAuthKey" + slug, null);
sharedPreferences.getString("pushAccountID" + slug, null);
PublicKey serverKey = null;
serverKey = deserializeRawPublicKey(Base64.decode(getServerKey(context, slug), Base64.URL_SAFE));
PrivateKey privateKey;
PublicKey publicKey;
byte[] authKey;
try {
KeyFactory kf = KeyFactory.getInstance("EC");
privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(pushPrivateKey, Base64.URL_SAFE)));
publicKey = kf.generatePublic(new X509EncodedKeySpec(Base64.decode(pushPublicKey, Base64.URL_SAFE)));
authKey = Base64.decode(encodedAuthKey, Base64.URL_SAFE);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
return null;
}
byte[] sharedSecret;
try {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(privateKey);
keyAgreement.doPhase(serverKey, true);
sharedSecret = keyAgreement.generateSecret();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
byte[] secondSaltInfo = "Content-Encoding: auth\0".getBytes(StandardCharsets.UTF_8);
byte[] deriveKey;
try {
deriveKey = deriveKey(authKey, sharedSecret, secondSaltInfo, 32);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
return null;
}
String decryptedStr;
try {
SecretKeySpec aesKey = new SecretKeySpec(deriveKey, "AES");
byte[] iv = Arrays.copyOfRange(messageEncrypted, 0, 12);
byte[] ciphertext = Arrays.copyOfRange(messageEncrypted, 12, messageEncrypted.length); // Separate ciphertext (the MAC is implicitly separated from the ciphertext)
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gCMParameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, aesKey, gCMParameterSpec);
byte[] decrypted = cipher.doFinal(ciphertext);
decryptedStr = new String(decrypted, 2, decrypted.length - 2, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
e.printStackTrace();
return null;
}
return restoreNotificationFromString(decryptedStr);
}
protected static PublicKey deserializeRawPublicKey(byte[] rawBytes) {
if (rawBytes.length != 65 && rawBytes.length != 64)
return null;
try {
KeyFactory kf = KeyFactory.getInstance("EC");
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write(P256_HEAD);
if (rawBytes.length == 64)
os.write(4);
os.write(rawBytes);
return kf.generatePublic(new X509EncodedKeySpec(os.toByteArray()));
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
e.printStackTrace();
}
return null;
}
private static byte[] deriveKey(byte[] firstSalt, byte[] secondSalt, byte[] info, int length) throws NoSuchAlgorithmException, InvalidKeyException {
Mac hmacContext = Mac.getInstance("HmacSHA256");
hmacContext.init(new SecretKeySpec(firstSalt, "HmacSHA256"));
byte[] hmac = hmacContext.doFinal(secondSalt);
hmacContext.init(new SecretKeySpec(hmac, "HmacSHA256"));
hmacContext.update(info);
byte[] result = hmacContext.doFinal(new byte[]{1});
return result.length <= length ? result : Arrays.copyOfRange(result, 0, length);
}
public String getPublicKey() {
return this.encodedPublicKey;
}
public String getAuthKey() {
return this.encodedAuthKey;
}
}

@ -257,6 +257,7 @@ public class Helper {
public static final String ARG_SEARCH_KEYWORD_CACHE = "ARG_SEARCH_KEYWORD_CACHE";
public static final String ARG_VIEW_MODEL_KEY = "ARG_VIEW_MODEL_KEY";
public static final String ARG_TAG_TIMELINE = "ARG_TAG_TIMELINE";
public static final String ARG_BUBBLE_TIMELINE = "ARG_BUBBLE_TIMELINE";
public static final String ARG_MEDIA_POSITION = "ARG_MEDIA_POSITION";
public static final String ARG_MEDIA_ATTACHMENT = "ARG_MEDIA_ATTACHMENT";
public static final String ARG_MEDIA_ATTACHMENTS = "ARG_MEDIA_ATTACHMENTS";
@ -1172,19 +1173,17 @@ public class Helper {
File files = new File(attachment.local_path);
float textSize = 15;
Paint paint = new Paint();
float textWidht = paint.measureText(waterMark);
float width = Helper.convertDpToPixel(textWidht, context);
float width = paint.measureText(waterMark, 0, waterMark.length());
try {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap backgroundBitmap = BitmapFactory.decodeFile(files.getAbsolutePath(), options);
int w = options.outWidth;
int h = options.outHeight;
float valx = (float) 1.0 - width / (float) w;
int w = backgroundBitmap.getWidth();
int h = backgroundBitmap.getHeight();
float valx = (float) 1.0 - ((Helper.convertDpToPixel(width, context) + 10)) / (float) w;
if (valx < 0)
valx = 0;
float valy = (h - Helper.convertDpToPixel(textSize, context) - 10) / (float) h;
float valy = (h - Helper.convertDpToPixel(textSize, context) - 0) / (float) h;
WatermarkText watermarkText = new WatermarkText(waterMark)
.setPositionX(valx)
.setPositionY(valy)
@ -1964,6 +1963,20 @@ public class Helper {
return R.style.AppTheme;
}
public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) {
if (MainActivity.filteredAccounts == null) {
MainActivity.filteredAccounts = new ArrayList<>();
}
if (!MainActivity.filteredAccounts.contains(target)) {
MainActivity.filteredAccounts.add(target);
}
}
public static void removeMutedAccount(app.fedilab.android.client.entities.api.Account target) {
if (MainActivity.filteredAccounts != null) {
MainActivity.filteredAccounts.remove(target);
}
}
//Enum that described actions to replace inside a toot content
public enum PatternType {
@ -1995,19 +2008,4 @@ public class Helper {
public interface OnFileCopied {
void onFileCopied(File file);
}
public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) {
if (MainActivity.filteredAccounts == null) {
MainActivity.filteredAccounts = new ArrayList<>();
}
if (!MainActivity.filteredAccounts.contains(target)) {
MainActivity.filteredAccounts.add(target);
}
}
public static void removeMutedAccount(app.fedilab.android.client.entities.api.Account target) {
if (MainActivity.filteredAccounts != null) {
MainActivity.filteredAccounts.remove(target);
}
}
}

@ -409,15 +409,6 @@ public class MediaHelper {
return maxHeight;
}
//Listener for recording media
public interface ActionRecord {
void onRecorded(String file);
}
public interface OnSchedule {
void scheduledAt(String scheduledDate);
}
public static void ResizedImageRequestBody(Context context, Uri uri, File targetedFile) {
InputStream decodeBitmapInputStream = null;
try {
@ -529,7 +520,6 @@ public class MediaHelper {
}
}
private static long getMaxSize(long maxSize) {
if (MainActivity.instanceInfo != null && MainActivity.instanceInfo.configuration != null && MainActivity.instanceInfo.configuration.media_attachments != null) {
maxSize = MainActivity.instanceInfo.configuration.media_attachments.image_size_limit;
@ -584,4 +574,14 @@ public class MediaHelper {
return null;
}
//Listener for recording media
public interface ActionRecord {
void onRecorded(String file);
}
public interface OnSchedule {
void scheduledAt(String scheduledDate);
}
}

@ -90,8 +90,13 @@ public class NotificationsHelper {
boolean notif_share = prefs.getBoolean(context.getString(R.string.SET_NOTIF_SHARE), true);
boolean notif_poll = prefs.getBoolean(context.getString(R.string.SET_NOTIF_POLL), true);
boolean notif_fav = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FAVOURITE), true);
boolean notif_status = prefs.getBoolean(context.getString(R.string.SET_NOTIF_STATUS), true);
boolean notif_updates = prefs.getBoolean(context.getString(R.string.SET_NOTIF_UPDATE), true);
boolean notif_signup = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_SIGNUP), true);
boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true);
//User disagree with all notifications
if (!notif_follow && !notif_fav && !notif_mention && !notif_share && !notif_poll)
if (!notif_follow && !notif_fav && !notif_mention && !notif_share && !notif_poll && !notif_status && !notif_updates && !notif_signup && !notif_report)
return; //Nothing is done
MastodonNotificationsService mastodonNotificationsService = init(context, slugArray[1]);

@ -14,6 +14,7 @@ package app.fedilab.android.helper;
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentUserID;
@ -22,7 +23,6 @@ import static app.fedilab.android.BaseMainActivity.show_replies;
import static app.fedilab.android.ui.pageadapter.FedilabPageAdapter.BOTTOM_TIMELINE_COUNT;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
@ -57,8 +57,10 @@ import java.util.regex.Pattern;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.client.entities.api.MastodonList;
import app.fedilab.android.client.entities.app.BottomMenu;
import app.fedilab.android.client.entities.app.BubbleTimeline;
import app.fedilab.android.client.entities.app.Pinned;
import app.fedilab.android.client.entities.app.PinnedTimeline;
import app.fedilab.android.client.entities.app.RemoteInstance;
@ -66,6 +68,8 @@ import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.client.entities.app.TagTimeline;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.ActivityMainBinding;
import app.fedilab.android.databinding.DialogBubbleExcludeVisibilityBinding;
import app.fedilab.android.databinding.DialogBubbleReplyVisibilityBinding;
import app.fedilab.android.databinding.TabCustomDefaultViewBinding;
import app.fedilab.android.databinding.TabCustomViewBinding;
import app.fedilab.android.exception.DBException;
@ -94,60 +98,6 @@ public class PinnedTimelineHelper {
}
/**
* Returns the slug of the first loaded fragment
*
* @param context - Context
* @param pinned - {@link Pinned}
* @param bottomMenu - {@link BottomMenu}
* @return String - slug
*/
public static String firstTimelineSlug(Context context, Pinned pinned, BottomMenu bottomMenu) {
String slug = Timeline.TimeLineEnum.HOME.getValue();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean singleBar = sharedpreferences.getBoolean(context.getString(R.string.SET_USE_SINGLE_TOPBAR), false);
PinnedTimeline pinnedTimelineMin = null;
if (singleBar) {
if (pinned != null && pinned.pinnedTimelines != null) {
for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) {
if (pinnedTimeline.displayed) {
if (pinnedTimelineMin == null) {
pinnedTimelineMin = pinnedTimeline;
} else if (pinnedTimelineMin.position > pinnedTimeline.position) {
pinnedTimelineMin = pinnedTimeline;
}
}
}
}
} else {
if (bottomMenu != null && bottomMenu.bottom_menu != null && bottomMenu.bottom_menu.size() > 0) {
BottomMenu.MenuItem menuItem = bottomMenu.bottom_menu.get(0);
return menuItem.item_menu_type.getValue();
}
}
String ident = null;
if (pinnedTimelineMin != null) {
if (pinnedTimelineMin.tagTimeline != null) {
ident = "@T@" + pinnedTimelineMin.tagTimeline.name;
if (pinnedTimelineMin.tagTimeline.isART) {
pinnedTimelineMin.type = Timeline.TimeLineEnum.ART;
}
} else if (pinnedTimelineMin.mastodonList != null) {
ident = "@l@" + pinnedTimelineMin.mastodonList.id;
} else if (pinnedTimelineMin.remoteInstance != null) {
if (pinnedTimelineMin.remoteInstance.type == RemoteInstance.InstanceType.NITTER) {
String remoteInstance = sharedpreferences.getString(context.getString(R.string.SET_NITTER_HOST), context.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
ident = "@R@" + remoteInstance;
} else {
ident = "@R@" + pinnedTimelineMin.remoteInstance.host;
}
}
slug = pinnedTimelineMin.type.getValue() + (ident != null ? "|" + ident : "");
}
return slug;
}
public synchronized static void redrawTopBarPinned(BaseMainActivity activity, ActivityMainBinding activityMainBinding, Pinned pinned, BottomMenu bottomMenu, List<MastodonList> mastodonLists) {
//Values must be initialized if there is no records in db
if (pinned == null) {
@ -159,8 +109,8 @@ public class PinnedTimelineHelper {
pinned.pinnedTimelines = new ArrayList<>();
}
//Set the slug of first visible fragment
String slugOfFirstFragment = PinnedTimelineHelper.firstTimelineSlug(activity, pinned, bottomMenu);
Helper.setSlugOfFirstFragment(activity, slugOfFirstFragment, currentUserID, currentInstance);
/*String slugOfFirstFragment = PinnedTimelineHelper.firstTimelineSlug(activity, pinned, bottomMenu);
Helper.setSlugOfFirstFragment(activity, slugOfFirstFragment, currentUserID, currentInstance);*/
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean singleBar = sharedpreferences.getBoolean(activity.getString(R.string.SET_USE_SINGLE_TOPBAR), false);
@ -187,6 +137,7 @@ public class PinnedTimelineHelper {
activityMainBinding.viewPager.setLayoutParams(params);
List<PinnedTimeline> pinnedTimelines = pinned.pinnedTimelines;
boolean extraFeatures = sharedpreferences.getBoolean(activity.getString(R.string.SET_EXTAND_EXTRA_FEATURES) + MainActivity.currentUserID + MainActivity.currentInstance, false);
if (singleBar) {
boolean createDefaultAtTop = true;
@ -222,15 +173,46 @@ public class PinnedTimelineHelper {
pinnedTimelineConversations.type = Timeline.TimeLineEnum.DIRECT;
pinnedTimelineConversations.position = 4;
pinned.pinnedTimelines.add(pinnedTimelineConversations);
try {
new Pinned(activity).updatePinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
}
}
if (extraFeatures) {
try {
Pinned pinnedAll = new Pinned(activity).getAllPinned(currentAccount);
if (pinnedAll == null) {
pinnedAll = new Pinned();
pinnedAll.user_id = currentUserID;
pinnedAll.instance = currentInstance;
pinnedAll.pinnedTimelines = new ArrayList<>();
}
boolean createDefaultBubbleAtTop = true;
for (PinnedTimeline pinnedTimeline : pinnedAll.pinnedTimelines) {
if (pinnedTimeline.type == Timeline.TimeLineEnum.BUBBLE) {
createDefaultBubbleAtTop = false;
break;
}
}
if (createDefaultBubbleAtTop) {
PinnedTimeline pinnedTimelineBubble = new PinnedTimeline();
pinnedTimelineBubble.type = Timeline.TimeLineEnum.BUBBLE;
pinnedTimelineBubble.position = pinnedAll.pinnedTimelines != null ? pinnedAll.pinnedTimelines.size() : 0;
pinned.pinnedTimelines.add(pinnedTimelineBubble);
boolean exist = new Pinned(activity).pinnedExist(pinned);
if (exist) {
new Pinned(activity).updatePinned(pinned);
} else {
new Pinned(activity).insertPinned(pinned);
}
}
} catch (DBException e) {
e.printStackTrace();
}
}
sortPositionAsc(pinnedTimelines);
//Check if changes occurred, if mastodonLists is null it does need, because it is the first call to draw pinned
boolean needRedraw = mastodonLists == null;
@ -421,6 +403,9 @@ public class PinnedTimelineHelper {
case DIRECT:
tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.ic_baseline_mail_24);
break;
case BUBBLE:
tabCustomDefaultViewBinding.icon.setImageResource(R.drawable.ic_baseline_bubble_chart_24);
break;
}
tab.setCustomView(tabCustomDefaultViewBinding.getRoot());
}
@ -522,6 +507,9 @@ public class PinnedTimelineHelper {
case TAG:
tagClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
break;
case BUBBLE:
bubbleClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
break;
case REMOTE:
if (pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER) {
instanceClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString());
@ -1004,6 +992,244 @@ public class PinnedTimelineHelper {
}
/**
* Manage long clicks on Bubble timelines
*
* @param activity - BaseMainActivity activity
* @param pinned - {@link Pinned}
* @param view - View
* @param position - int position of the tab
*/
public static void bubbleClick(BaseMainActivity activity, Pinned pinned, View view, ActivityMainBinding activityMainBinding, int position, String slug) {
int toRemove = itemToRemoveInBottomMenu(activity);
PopupMenu popup = new PopupMenu(activity, view);
int offSetPosition = position - (BOTTOM_TIMELINE_COUNT - toRemove);
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean singleBar = sharedpreferences.getBoolean(activity.getString(R.string.SET_USE_SINGLE_TOPBAR), false);
if (singleBar) {
offSetPosition = position;
}
if (pinned.pinnedTimelines.get(offSetPosition).bubbleTimeline == null) {
pinned.pinnedTimelines.get(offSetPosition).bubbleTimeline = new BubbleTimeline();
}
BubbleTimeline bubbleTimeline = pinned.pinnedTimelines.get(offSetPosition).bubbleTimeline;
popup.getMenuInflater()
.inflate(R.menu.option_bubble_timeline, popup.getMenu());
Menu menu = popup.getMenu();
final MenuItem itemMediaOnly = menu.findItem(R.id.action_show_media_only);
final MenuItem itemRemote = menu.findItem(R.id.action_remote);
final boolean[] changes = {false};
final boolean[] mediaOnly = {false};
final boolean[] remote = {false};
mediaOnly[0] = bubbleTimeline.only_media;
remote[0] = bubbleTimeline.remote;
itemMediaOnly.setChecked(mediaOnly[0]);
itemRemote.setChecked(remote[0]);
popup.setOnDismissListener(menu1 -> {
if (changes[0]) {
if (activityMainBinding.viewPager.getAdapter() != null) {
try {
new StatusCache(activity).deleteForSlug(slug);
} catch (DBException e) {
e.printStackTrace();
}
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null);
editor.commit();
Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition());
if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) {
FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction();
fragTransaction.detach(fragmentMastodonTimeline).commit();
Bundle bundle = new Bundle();
bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.BUBBLE);
bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, bubbleTimeline);
bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false);
fragmentMastodonTimeline.setArguments(bundle);
FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction();
fragTransaction2.attach(fragmentMastodonTimeline);
fragTransaction2.commit();
((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate();
}
}
}
});
int finalOffSetPosition = offSetPosition;
popup.setOnMenuItemClickListener(item -> {
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
item.setActionView(new View(activity));
item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return false;
}
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
return false;
}
});
changes[0] = true;
int itemId = item.getItemId();
if (itemId == R.id.action_show_media_only) {
mediaOnly[0] = !mediaOnly[0];
bubbleTimeline.only_media = mediaOnly[0];
pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline;
itemMediaOnly.setChecked(mediaOnly[0]);
try {
new Pinned(activity).updatePinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
} else if (itemId == R.id.action_remote) {
remote[0] = !remote[0];
bubbleTimeline.remote = remote[0];
pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline;
itemRemote.setChecked(remote[0]);
try {
new Pinned(activity).updatePinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
} else if (itemId == R.id.action_exclude_visibility) {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
DialogBubbleExcludeVisibilityBinding dialogBinding = DialogBubbleExcludeVisibilityBinding.inflate(activity.getLayoutInflater());
dialogBuilder.setView(dialogBinding.getRoot());
dialogBuilder.setTitle(R.string.exclude_visibility);
if (bubbleTimeline.exclude_visibilities == null) {
bubbleTimeline.exclude_visibilities = new ArrayList<>();
}
for (String value : bubbleTimeline.exclude_visibilities) {
if (value.equalsIgnoreCase("public")) {
dialogBinding.valuePublic.setChecked(true);
}
if (value.equalsIgnoreCase("local")) {
dialogBinding.valueLocal.setChecked(true);
}
if (value.equalsIgnoreCase("direct")) {
dialogBinding.valueDirect.setChecked(true);
}
if (value.equalsIgnoreCase("list")) {
dialogBinding.valueList.setChecked(true);
}
if (value.equalsIgnoreCase("private")) {
dialogBinding.valuePrivate.setChecked(true);
}
if (value.equalsIgnoreCase("unlisted")) {
dialogBinding.valueUnlisted.setChecked(true);
}
}
dialogBinding.valuePrivate.setOnCheckedChangeListener((compoundButton, checked) -> {
if (checked) {
if (!bubbleTimeline.exclude_visibilities.contains("private")) {
bubbleTimeline.exclude_visibilities.add("private");
}
} else {
bubbleTimeline.exclude_visibilities.remove("private");
}
});
dialogBinding.valueDirect.setOnCheckedChangeListener((compoundButton, checked) -> {
if (checked) {
if (!bubbleTimeline.exclude_visibilities.contains("direct")) {
bubbleTimeline.exclude_visibilities.add("direct");
}
} else {
bubbleTimeline.exclude_visibilities.remove("direct");
}
});
dialogBinding.valueList.setOnCheckedChangeListener((compoundButton, checked) -> {
if (checked) {
if (!bubbleTimeline.exclude_visibilities.contains("list")) {
bubbleTimeline.exclude_visibilities.add("list");
}
} else {
bubbleTimeline.exclude_visibilities.remove("list");
}
});
dialogBinding.valueLocal.setOnCheckedChangeListener((compoundButton, checked) -> {
if (checked) {
if (!bubbleTimeline.exclude_visibilities.contains("local")) {
bubbleTimeline.exclude_visibilities.add("local");
}
} else {
bubbleTimeline.exclude_visibilities.remove("local");
}
});
dialogBinding.valuePublic.setOnCheckedChangeListener((compoundButton, checked) -> {
if (checked) {
if (!bubbleTimeline.exclude_visibilities.contains("public")) {
bubbleTimeline.exclude_visibilities.add("public");
}
} else {
bubbleTimeline.exclude_visibilities.remove("public");
}
});
dialogBinding.valueUnlisted.setOnCheckedChangeListener((compoundButton, checked) -> {
if (checked) {
if (!bubbleTimeline.exclude_visibilities.contains("unlisted")) {
bubbleTimeline.exclude_visibilities.add("unlisted");
}
} else {
bubbleTimeline.exclude_visibilities.remove("unlisted");
}
});
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline;
try {
new Pinned(activity).updatePinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
});
AlertDialog alertDialog = dialogBuilder.create();
alertDialog.show();
} else if (itemId == R.id.action_reply_visibility) {
AlertDialog.Builder dialogBuilder;
AlertDialog alertDialog;
dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle());
DialogBubbleReplyVisibilityBinding dialogBinding = DialogBubbleReplyVisibilityBinding.inflate(activity.getLayoutInflater());
dialogBuilder.setView(dialogBinding.getRoot());
dialogBuilder.setTitle(R.string.reply_visibility);
int checkedId = R.id.all;
if (bubbleTimeline.reply_visibility != null && bubbleTimeline.reply_visibility.equalsIgnoreCase("following")) {
checkedId = R.id.following;
} else if (bubbleTimeline.reply_visibility != null && bubbleTimeline.reply_visibility.equalsIgnoreCase("self")) {
checkedId = R.id.self;
}
dialogBinding.replyVisibility.check(checkedId);
dialogBinding.replyVisibility.setOnCheckedChangeListener((radioGroup, checkedElement) -> {
if (checkedElement == R.id.all) {
bubbleTimeline.reply_visibility = null;
} else if (checkedElement == R.id.following) {
bubbleTimeline.reply_visibility = "following";
} else if (checkedElement == R.id.self) {
bubbleTimeline.reply_visibility = "self";
}
});
dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> {
pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline;
try {
new Pinned(activity).updatePinned(pinned);
} catch (DBException e) {
e.printStackTrace();
}
});
alertDialog = dialogBuilder.create();
alertDialog.show();
}
return false;
});
popup.show();
}
/**
* Manage long clicks on followed instances
*

@ -15,9 +15,6 @@ package app.fedilab.android.helper;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.helper.ECDH.kp_private;
import static app.fedilab.android.helper.ECDH.kp_public;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
@ -26,10 +23,8 @@ import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.endpoints.MastodonNotificationsService;
import app.fedilab.android.client.entities.api.PushSubscription;
@ -50,24 +45,18 @@ public class PushNotifications {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String strPub = prefs.getString(kp_public + slug, "");
String strPriv = prefs.getString(kp_private + slug, "");
ECDH ecdh = null;
ECDHFedilab ecdh = null;
try {
ecdh = ECDH.getInstance(slug);
ecdh = new ECDHFedilab(context, slug);
} catch (Exception e) {
e.printStackTrace();
}
if (ecdh == null) {
return;
}
if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) {
ecdh.newPair(context);
}
String pubKey = ecdh.getPublicKey(context);
byte[] randBytes = new byte[16];
new Random().nextBytes(randBytes);
String auth = ECDH.base64Encode(randBytes);
String pubKey = ecdh.getPublicKey();
String auth = ecdh.getAuthKey();
boolean notif_follow = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FOLLOW), true);
@ -75,8 +64,10 @@ public class PushNotifications {
boolean notif_share = prefs.getBoolean(context.getString(R.string.SET_NOTIF_SHARE), true);
boolean notif_poll = prefs.getBoolean(context.getString(R.string.SET_NOTIF_POLL), true);
boolean notif_fav = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FAVOURITE), true);
MastodonNotificationsService mastodonNotificationsService = init(context, BaseMainActivity.currentInstance);
ECDH finalEcdh = ecdh;
boolean notif_status = prefs.getBoolean(context.getString(R.string.SET_NOTIF_STATUS), true);
boolean notif_updates = prefs.getBoolean(context.getString(R.string.SET_NOTIF_UPDATE), true);
boolean notif_signup = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_SIGNUP), true);
boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true);
new Thread(() -> {
String[] slugArray = slug.split("@");
BaseAccount accountDb = null;
@ -85,9 +76,11 @@ public class PushNotifications {
} catch (DBException e) {
e.printStackTrace();
}
if (accountDb == null) {
return;
}
MastodonNotificationsService mastodonNotificationsService = init(context, accountDb.instance);
PushSubscription pushSubscription;
Call<PushSubscription> pushSubscriptionCall = mastodonNotificationsService.pushSubscription(
accountDb.token,
@ -98,14 +91,23 @@ public class PushNotifications {
notif_fav,
notif_share,
notif_mention,
notif_poll);
notif_poll,
notif_status,
notif_updates,
notif_signup,
notif_report);
if (pushSubscriptionCall != null) {
try {
Response<PushSubscription> pushSubscriptionResponse = pushSubscriptionCall.execute();
if (pushSubscriptionResponse.isSuccessful()) {
pushSubscription = pushSubscriptionResponse.body();
if (pushSubscription != null) {
finalEcdh.saveServerKey(context, pushSubscription.server_key);
pushSubscription.server_key = pushSubscription.server_key.replace('/', '_');
pushSubscription.server_key = pushSubscription.server_key.replace('+', '-');
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
prefsEditor.putString("server_key" + slug, pushSubscription.server_key);
prefsEditor.apply();
}
}
} catch (Exception e) {
@ -122,6 +124,7 @@ public class PushNotifications {
}
public static String getToken(Context context, String slug) {
return context.getSharedPreferences("unifiedpush.connector", Context.MODE_PRIVATE).getString(
slug + "/unifiedpush.connector", null);

@ -70,7 +70,6 @@ public class ThemeHelper {
}
/**
* Animate two views, the current view will be hidden to left
*
@ -230,7 +229,6 @@ public class ThemeHelper {
}
/**
* Allow to set colors for having description on media
*

@ -71,6 +71,8 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
private final ConstraintSet mConstraintSet = new ConstraintSet();
PhotoEditor mPhotoEditor;
String path;
CropImageContractOptions cropImageContractOptions;
ActivityResultLauncher<CropImageContractOptions> cropImageContractOptionsActivityResultLauncher;
private PropertiesBSFragment mPropertiesBSFragment;
private ShapeBSFragment mShapeBSFragment;
private ShapeBuilder mShapeBuilder;
@ -79,8 +81,6 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
private Uri uri;
private boolean exit;
private ActivityEditImageBinding binding;
CropImageContractOptions cropImageContractOptions;
ActivityResultLauncher<CropImageContractOptions> cropImageContractOptionsActivityResultLauncher;
private static int exifToDegrees(int exifOrientation) {
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {

@ -218,6 +218,9 @@ public class ComposeWorker extends Worker {
return;
}
String language = sharedPreferences.getString(context.getString(R.string.SET_COMPOSE_LANGUAGE) + dataPost.userId + dataPost.instance, null);
if (statuses.get(i).local_only) {
statuses.get(i).text += " \uD83D\uDC41";
}
if (dataPost.scheduledDate == null) {
if (dataPost.statusEditId == null) {
statusCall = mastodonStatusesService.createStatus(null, dataPost.token, statuses.get(i).text, attachmentIds, poll_options, poll_expire_in,
@ -267,7 +270,11 @@ public class ComposeWorker extends Worker {
b.putBoolean(Helper.RECEIVE_COMPOSE_ERROR_MESSAGE, true);
Intent intentBD = new Intent(Helper.INTENT_COMPOSE_ERROR_MESSAGE);
b.putSerializable(Helper.ARG_STATUS_DRAFT, dataPost.statusDraft);
b.putSerializable(Helper.RECEIVE_ERROR_MESSAGE, statusResponse.errorBody().string());
String err = statusResponse.errorBody().string();
if (err.contains("{\"error\":\"")) {
err = err.replaceAll("\\{\"error\":\"(.*)\"\\}", "$1");
}
b.putSerializable(Helper.RECEIVE_ERROR_MESSAGE, err);
intentBD.putExtras(b);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentBD);
return;

@ -40,11 +40,12 @@ public class CustomReceiver extends MessagingReceiver {
// Called when a new message is received. The message contains the full POST body of the push message
new Thread(() -> {
try {
/* ECDH ecdh = ECDH.getInstance(slug);
if (ecdh == null) {
return;
}*/
//String decrypted = ecdh.uncryptMessage(context, String.valueOf(message));
/*Notification notification = ECDHFedilab.decryptNotification(context, slug, message);
Log.v(Helper.TAG,"notification: " + notification);
if(notification != null) {
Log.v(Helper.TAG,"id: " + notification.id);
}
*/
NotificationsHelper.task(context, slug);
} catch (Exception e) {
e.printStackTrace();

@ -56,8 +56,8 @@ import es.dmoral.toasty.Toasty;
public class AccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<Account> accountList;
private Context context;
private final boolean home_mute;
private Context context;
public AccountAdapter(List<Account> accountList, boolean home_mute) {
this.accountList = accountList;

@ -68,7 +68,6 @@ import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
@ -150,6 +149,30 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
"..--..", ".-.-.-", ".----.", "-.-.--", "-..-.", "-.--.", "-.--.-", ".-...", "---...", "-.-.-.", "-...-", ".-.-.", "-....-", "..--.-",
".-..-.", "...-..-", ".--.-.", "..-.-", "--...-"
};
public static int currentCursorPosition;
private final List<Status> statusList;
private final int TYPE_NORMAL = 0;
private final BaseAccount account;
private final String visibility;
private final app.fedilab.android.client.entities.api.Account mentionedAccount;
private final String editMessageId;
public ManageDrafts manageDrafts;
public promptDraftListener promptDraftListener;
private int statusCount;
private Context context;
private AlertDialog alertDialogEmoji;
private List<Emoji> emojisList = new ArrayList<>();
private boolean unlisted_changed = false;
public ComposeAdapter(List<Status> statusList, int statusCount, BaseAccount account, app.fedilab.android.client.entities.api.Account mentionedAccount, String visibility, String editMessageId) {
this.statusList = statusList;
this.statusCount = statusCount;
this.account = account;
this.mentionedAccount = mentionedAccount;
this.visibility = visibility;
this.editMessageId = editMessageId;
}
public static int countMorseChar(String content) {
int count_char = 0;
@ -180,29 +203,34 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
return morseContent;
}
private final List<Status> statusList;
private final int TYPE_NORMAL = 0;
private final BaseAccount account;
private final String visibility;
private final app.fedilab.android.client.entities.api.Account mentionedAccount;
private final String editMessageId;
public ManageDrafts manageDrafts;
private int statusCount;
private Context context;
private AlertDialog alertDialogEmoji;
private List<Emoji> emojisList = new ArrayList<>();
public promptDraftListener promptDraftListener;
private boolean unlisted_changed = false;
public static int currentCursorPosition;
private static void updateCharacterCount(ComposeViewHolder composeViewHolder) {
int charCount = MastodonHelper.countLength(composeViewHolder);
composeViewHolder.binding.characterCount.setText(String.valueOf(charCount));
composeViewHolder.binding.characterProgress.setProgress(charCount);
public ComposeAdapter(List<Status> statusList, int statusCount, BaseAccount account, app.fedilab.android.client.entities.api.Account mentionedAccount, String visibility, String editMessageId) {
this.statusList = statusList;
this.statusCount = statusCount;
this.account = account;
this.mentionedAccount = mentionedAccount;
this.visibility = visibility;
this.editMessageId = editMessageId;
}
public static StatusDraft prepareDraft(List<Status> statusList, ComposeAdapter composeAdapter, String instance, String user_id) {
//Collect all statusCompose
List<Status> statusDrafts = new ArrayList<>();
List<Status> statusReplies = new ArrayList<>();
int i = 0;
for (Status status : statusList) {
//Statuses must be sent
if (composeAdapter.getItemViewType(i) == TYPE_COMPOSE) {
statusDrafts.add(status);
} else {
statusReplies.add(status);
}
i++;
}
StatusDraft statusDraftDB = new StatusDraft();
statusDraftDB.statusReplyList = statusReplies;
statusDraftDB.statusDraftList = statusDrafts;
statusDraftDB.instance = instance;
statusDraftDB.user_id = user_id;
return statusDraftDB;
}
/**
@ -231,7 +259,6 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
});
}
/**
* Add an attachment from ComposeActivity
*
@ -255,36 +282,6 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
private static void updateCharacterCount(ComposeViewHolder composeViewHolder) {
int charCount = MastodonHelper.countLength(composeViewHolder);
composeViewHolder.binding.characterCount.setText(String.valueOf(charCount));
composeViewHolder.binding.characterProgress.setProgress(charCount);
}
public static StatusDraft prepareDraft(List<Status> statusList, ComposeAdapter composeAdapter, String instance, String user_id) {
//Collect all statusCompose
List<Status> statusDrafts = new ArrayList<>();
List<Status> statusReplies = new ArrayList<>();
int i = 0;
for (Status status : statusList) {
//Statuses must be sent
if (composeAdapter.getItemViewType(i) == TYPE_COMPOSE) {
statusDrafts.add(status);
} else {
statusReplies.add(status);
}
i++;
}
StatusDraft statusDraftDB = new StatusDraft();
statusDraftDB.statusReplyList = statusReplies;
statusDraftDB.statusDraftList = statusDrafts;
statusDraftDB.instance = instance;
statusDraftDB.user_id = user_id;
return statusDraftDB;
}
//Create text when mentioning a toot
public void loadMentions(Status status) {
//Get the first draft
@ -1025,19 +1022,18 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
for (Attachment attachment : attachmentList) {
ComposeAttachmentItemBinding composeAttachmentItemBinding = ComposeAttachmentItemBinding.inflate(LayoutInflater.from(context), holder.binding.attachmentsList, false);
composeAttachmentItemBinding.buttonPlay.setVisibility(View.GONE);
if (editMessageId != null) {
composeAttachmentItemBinding.editPreview.setVisibility(View.INVISIBLE);
if (editMessageId != null && attachment.url != null) {
composeAttachmentItemBinding.editPreview.setVisibility(View.GONE);
composeAttachmentItemBinding.buttonDescription.setVisibility(View.INVISIBLE);
composeAttachmentItemBinding.buttonOrderDown.setVisibility(View.INVISIBLE);
composeAttachmentItemBinding.buttonOrderUp.setVisibility(View.INVISIBLE);
composeAttachmentItemBinding.buttonRemove.setVisibility(View.INVISIBLE);
}
String attachmentPath = attachment.local_path != null && !attachment.local_path.trim().isEmpty() ? attachment.local_path : attachment.preview_url;
if (attachment.type != null || attachment.mimeType != null) {
if ((attachment.type != null && attachment.type.toLowerCase().startsWith("image")) || (attachment.mimeType != null && attachment.mimeType.toLowerCase().startsWith("image"))) {
Glide.with(composeAttachmentItemBinding.preview.getContext())
.load(attachmentPath)
.diskCacheStrategy(DiskCacheStrategy.NONE)
//.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.into(composeAttachmentItemBinding.preview);
} else if ((attachment.type != null && attachment.type.toLowerCase().startsWith("video")) || (attachment.mimeType != null && attachment.mimeType.toLowerCase().startsWith("video"))) {
@ -1280,7 +1276,11 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
holder.binding.buttonEmojiOne.setVisibility(View.VISIBLE);
if (extraFeatures) {
boolean displayLocalOnly = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_LOCAL_ONLY) + MainActivity.currentUserID + MainActivity.currentInstance, true);
holder.binding.buttonTextFormat.setVisibility(View.VISIBLE);
if (displayLocalOnly) {
holder.binding.buttonLocalOnly.setVisibility(View.VISIBLE);
}
holder.binding.buttonTextFormat.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle());
builder.setTitle(context.getString(R.string.post_format));
@ -1306,6 +1306,28 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builder.create().show();
});
holder.binding.buttonLocalOnly.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle());
builder.setTitle(context.getString(R.string.local_only));
Resources res = context.getResources();
boolean[] valArr = new boolean[]{false, true};
String[] labelArr = res.getStringArray(R.array.set_local_only);
int selection = 0;
int localOnly = sharedpreferences.getInt(context.getString(R.string.SET_COMPOSE_LOCAL_ONLY) + account.user_id + account.instance, 0);
if (statusDraft.local_only || localOnly == 1) {
selection = 1;
}
builder.setSingleChoiceItems(labelArr, selection, null);
builder.setPositiveButton(R.string.validate, (dialog, which) -> {
int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
statusDraft.local_only = valArr[selectedPosition];
notifyItemChanged(holder.getLayoutPosition());
dialog.dismiss();
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builder.create().show();
});
} else {
holder.binding.buttonTextFormat.setVisibility(View.GONE);
}
@ -1442,7 +1464,8 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
statusDraft.visibility = MastodonHelper.visibility.PUBLIC.name();
unlisted_changed = true;
});
if (statusDraft.spoilerChecked) {
if (statusDraft.spoilerChecked || statusDraft.spoiler_text != null && statusDraft.spoiler_text.trim().length() > 0) {
holder.binding.contentSpoiler.setVisibility(View.VISIBLE);
} else {
holder.binding.contentSpoiler.setVisibility(View.GONE);
@ -1463,7 +1486,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
holder.binding.buttonEmoji.setOnClickListener(v -> {
try {
displayEmojiPicker(holder);
displayEmojiPicker(holder, account.instance);
} catch (DBException e) {
e.printStackTrace();
}
@ -1850,7 +1873,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
* @param holder - view for the message {@link ComposeViewHolder}
* @throws DBException
*/
private void displayEmojiPicker(ComposeViewHolder holder) throws DBException {
private void displayEmojiPicker(ComposeViewHolder holder, String instance) throws DBException {
final AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle());
int paddingPixel = 15;
@ -1860,10 +1883,10 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
builder.setTitle(R.string.insert_emoji);
if (emojis != null && emojis.size() > 0) {
GridView gridView = new GridView(context);
gridView.setAdapter(new EmojiAdapter(emojis.get(BaseMainActivity.currentInstance)));
gridView.setAdapter(new EmojiAdapter(emojis.get(instance)));
gridView.setNumColumns(5);
gridView.setOnItemClickListener((parent, view, position, id) -> {
holder.binding.content.getText().insert(holder.binding.content.getSelectionStart(), " :" + emojis.get(BaseMainActivity.currentInstance).get(position).shortcode + ": ");
holder.binding.content.getText().insert(holder.binding.content.getSelectionStart(), " :" + emojis.get(instance).get(position).shortcode + ": ");
alertDialogEmoji.dismiss();
});
gridView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp);

@ -69,22 +69,6 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
this.conversationList = conversations;
}
public int getCount() {
return conversationList.size();
}
public Conversation getItem(int position) {
return conversationList.get(position);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
DrawerConversationBinding itemBinding = DrawerConversationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ConversationHolder(itemBinding);
}
public static void applyColorConversation(Context context, ConversationHolder holder) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
@ -123,6 +107,22 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
}
public int getCount() {
return conversationList.size();
}
public Conversation getItem(int position) {
return conversationList.get(position);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
DrawerConversationBinding itemBinding = DrawerConversationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ConversationHolder(itemBinding);
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {

@ -37,8 +37,8 @@ import app.fedilab.android.databinding.DrawerFieldBinding;
public class FieldAdapter extends RecyclerView.Adapter<FieldAdapter.FieldViewHolder> {
private final List<Field> fields;
private Context context;
private final Account account;
private Context context;
public FieldAdapter(List<Field> fields, Account account) {
this.fields = fields;

@ -40,8 +40,12 @@ import app.fedilab.android.helper.Helper;
public class InstanceRegAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<JoinMastodonInstance> joinMastodonInstanceList;
private Context context;
public ActionClick actionClick;
private Context context;
public InstanceRegAdapter(List<JoinMastodonInstance> joinMastodonInstanceList) {
this.joinMastodonInstanceList = joinMastodonInstanceList;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
@ -67,11 +71,6 @@ public class InstanceRegAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
holder.binding.watchTrendig.setOnClickListener(v -> actionClick.trends(position));
}
public InstanceRegAdapter(List<JoinMastodonInstance> joinMastodonInstanceList) {
this.joinMastodonInstanceList = joinMastodonInstanceList;
}
public int getCount() {
return joinMastodonInstanceList.size();
}
@ -88,12 +87,6 @@ public class InstanceRegAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
return new ViewHolder(itemBinding);
}
public interface ActionClick {
void instance(int position);
void trends(int position);
}
public long getItemId(int position) {
return position;
}
@ -103,6 +96,12 @@ public class InstanceRegAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
return joinMastodonInstanceList.size();
}
public interface ActionClick {
void instance(int position);
void trends(int position);
}
public interface RecyclerViewClickListener {
void recyclerViewListClicked(View v, int position);
}

@ -47,6 +47,7 @@ import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.DrawerFollowBinding;
import app.fedilab.android.databinding.DrawerStatusFilteredBinding;
import app.fedilab.android.databinding.DrawerStatusFilteredHideBinding;
import app.fedilab.android.databinding.DrawerStatusNotificationBinding;
import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding;
import app.fedilab.android.helper.Helper;
@ -70,6 +71,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
private final int TYPE_FILERED = 10;
private final int TYPE_ADMIN_SIGNUP = 11;
private final int TYPE_ADMIN_REPORT = 12;
private final int TYPE_HIDDEN = 13;
public FetchMoreCallBack fetchMoreCallBack;
private Context context;
private RecyclerView mRecyclerView;
@ -78,6 +80,50 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
this.notificationList = notificationList;
}
public static void applyColorAccount(Context context, ViewHolderFollow holder) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
boolean customLight = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_LIGHT_COLORS), false);
boolean customDark = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_DARK_COLORS), false);
int theme_icons_color = -1;
int theme_statuses_color = -1;
int theme_text_color = -1;
int theme_text_header_1_line = -1;
int theme_text_header_2_line = -1;
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) { //LIGHT THEME
if (customLight) {
theme_icons_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_ICON), -1);
theme_statuses_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_BACKGROUND), -1);
theme_text_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_TEXT), -1);
theme_text_header_1_line = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_DISPLAY_NAME), -1);
theme_text_header_2_line = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_USERNAME), -1);
}
} else {
if (customDark) {
theme_icons_color = sharedpreferences.getInt(context.getString(R.string.SET_DARK_ICON), -1);
theme_statuses_color = sharedpreferences.getInt(context.getString(R.string.SET_DARK_BACKGROUND), -1);
theme_text_color = sharedpreferences.getInt(context.getString(R.string.SET_DARK_TEXT), -1);
theme_text_header_1_line = sharedpreferences.getInt(context.getString(R.string.SET_DARK_DISPLAY_NAME), -1);
theme_text_header_2_line = sharedpreferences.getInt(context.getString(R.string.SET_DARK_USERNAME), -1);
}
}
if (theme_text_color != -1) {
holder.binding.title.setTextColor(theme_text_color);
}
if (theme_icons_color != -1) {
Helper.changeDrawableColor(context, holder.binding.icon, theme_icons_color);
}
if (theme_statuses_color != -1) {
holder.binding.cardviewContainer.setBackgroundColor(theme_statuses_color);
}
if (theme_text_header_1_line != -1) {
holder.binding.displayName.setTextColor(theme_text_header_1_line);
}
if (theme_text_header_2_line != -1) {
holder.binding.username.setTextColor(theme_text_header_2_line);
}
}
public int getCount() {
return notificationList.size();
}
@ -100,30 +146,58 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
case "follow_request":
return TYPE_FOLLOW_REQUEST;
case "mention":
return TYPE_MENTION;
if (notificationList.get(position).status != null) {
return TYPE_MENTION;
} else {
return TYPE_HIDDEN;
}
case "reblog":
return TYPE_REBLOG;
if (notificationList.get(position).status != null) {
return TYPE_REBLOG;
} else {
return TYPE_HIDDEN;
}
case "update":
return TYPE_UPDATE;
if (notificationList.get(position).status != null) {
return TYPE_UPDATE;
} else {
return TYPE_HIDDEN;
}
case "favourite":
return TYPE_FAVOURITE;
if (notificationList.get(position).status != null) {
return TYPE_FAVOURITE;
} else {
return TYPE_HIDDEN;
}
case "poll":
return TYPE_POLL;
if (notificationList.get(position).status != null) {
return TYPE_POLL;
} else {
return TYPE_HIDDEN;
}
case "status":
return TYPE_STATUS;
if (notificationList.get(position).status != null) {
return TYPE_STATUS;
} else {
return TYPE_HIDDEN;
}
case "admin.sign_up":
return TYPE_ADMIN_SIGNUP;
case "admin.report":
return TYPE_ADMIN_REPORT;
case "pleroma:emoji_reaction":
return TYPE_REACTION;
if (notificationList.get(position).status != null) {
return TYPE_REACTION;
} else {
return TYPE_HIDDEN;
}
}
}
return super.getItemViewType(position);
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
@ -139,56 +213,15 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
} else if (viewType == TYPE_FILERED) {
DrawerStatusFilteredBinding itemBinding = DrawerStatusFilteredBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusAdapter.StatusViewHolder(itemBinding);
} else if (viewType == TYPE_HIDDEN) {
DrawerStatusFilteredHideBinding itemBinding = DrawerStatusFilteredHideBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusAdapter.StatusViewHolder(itemBinding);
} else {
DrawerStatusNotificationBinding itemBinding = DrawerStatusNotificationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusAdapter.StatusViewHolder(itemBinding);
}
}
public static void applyColorAccount(Context context, ViewHolderFollow holder) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
boolean customLight = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_LIGHT_COLORS), false);
boolean customDark = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_DARK_COLORS), false);
int theme_icons_color = -1;
int theme_statuses_color = -1;
int theme_text_color = -1;
int theme_text_header_1_line = -1;
int theme_text_header_2_line = -1;
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) { //LIGHT THEME
if (customLight) {
theme_icons_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_ICON), -1);
theme_statuses_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_BACKGROUND), -1);
theme_text_color = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_TEXT), -1);
theme_text_header_1_line = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_DISPLAY_NAME), -1);
theme_text_header_2_line = sharedpreferences.getInt(context.getString(R.string.SET_LIGHT_USERNAME), -1);
}
} else {
if (customDark) {
theme_icons_color = sharedpreferences.getInt(context.getString(R.string.SET_DARK_ICON), -1);
theme_statuses_color = sharedpreferences.getInt(context.getString(R.string.SET_DARK_BACKGROUND), -1);
theme_text_color = sharedpreferences.getInt(context.getString(R.string.SET_DARK_TEXT), -1);
theme_text_header_1_line = sharedpreferences.getInt(context.getString(R.string.SET_DARK_DISPLAY_NAME), -1);
theme_text_header_2_line = sharedpreferences.getInt(context.getString(R.string.SET_DARK_USERNAME), -1);
}
}
if (theme_text_color != -1) {
holder.binding.title.setTextColor(theme_text_color);
}
if (theme_icons_color != -1) {
Helper.changeDrawableColor(context, holder.binding.icon, theme_icons_color);
}
if (theme_statuses_color != -1) {
holder.binding.cardviewContainer.setBackgroundColor(theme_statuses_color);
}
if (theme_text_header_1_line != -1) {
holder.binding.displayName.setTextColor(theme_text_header_1_line);
}
if (theme_text_header_2_line != -1) {
holder.binding.username.setTextColor(theme_text_header_2_line);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
Notification notification = notificationList.get(position);
@ -284,7 +317,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
notification.filteredByApp = null;
notifyItemChanged(position);
});
} else {
} else if (notification.status != null) {
StatusAdapter.StatusViewHolder holderStatus = (StatusAdapter.StatusViewHolder) viewHolder;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedpreferences.getBoolean(context.getString(R.string.SET_CARDVIEW), false)) {
@ -437,7 +470,6 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
notification.account.getSpanDisplayNameTitle(context,
new WeakReference<>(holderStatus.bindingNotification.status.displayName), title),
TextView.BufferType.SPANNABLE);
holderStatus.bindingNotification.status.displayName.setText(title, TextView.BufferType.SPANNABLE);
holderStatus.bindingNotification.status.username.setText(String.format("@%s", notification.account.acct));
holderStatus.bindingNotification.status.actionButtons.setVisibility(View.GONE);
}

@ -141,6 +141,10 @@ public class ReorderTabAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
holder.binding.icon.setImageResource(R.drawable.ic_baseline_mail_24);
holder.binding.text.setText(R.string.v_direct);
break;
case BUBBLE:
holder.binding.icon.setImageResource(R.drawable.ic_baseline_bubble_chart_24);
holder.binding.text.setText(R.string.bubble);
break;
}

@ -0,0 +1,97 @@
package app.fedilab.android.ui.drawer;
/* Copyright 2023 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.core.app.ActivityOptionsCompat;
import com.bumptech.glide.Glide;
import com.smarteist.autoimageslider.SliderViewAdapter;
import java.util.ArrayList;
import java.util.List;
import app.fedilab.android.activities.MediaActivity;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.databinding.DrawerSliderBinding;
import app.fedilab.android.helper.Helper;
public class SliderAdapter extends SliderViewAdapter<SliderAdapter.SliderAdapterVH> {
private final Status status;
private final List<Attachment> mSliderItems;
private Context context;
public SliderAdapter(Status status) {
this.status = status;
this.mSliderItems = status.media_attachments;
}
public void addItem(Attachment sliderItem) {
this.mSliderItems.add(sliderItem);
notifyDataSetChanged();
}
@Override
public SliderAdapterVH onCreateViewHolder(ViewGroup parent) {
context = parent.getContext();
DrawerSliderBinding itemBinding = DrawerSliderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new SliderAdapterVH(itemBinding);
}
@Override
public void onBindViewHolder(SliderAdapterVH viewHolder, final int position) {
Attachment sliderItem = mSliderItems.get(position);
Glide.with(viewHolder.itemView)
.load(sliderItem.preview_url)
.centerCrop()
.into(viewHolder.binding.ivAutoImageSlider);
viewHolder.itemView.setOnClickListener(v -> {
Intent mediaIntent = new Intent(context, MediaActivity.class);
Bundle b = new Bundle();
b.putInt(Helper.ARG_MEDIA_POSITION, position + 1);
b.putSerializable(Helper.ARG_MEDIA_ARRAY, new ArrayList<>(status.media_attachments));
mediaIntent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation((Activity) context, viewHolder.binding.ivAutoImageSlider, status.media_attachments.get(0).url);
// start the new activity
context.startActivity(mediaIntent, options.toBundle());
});
}
@Override
public int getCount() {
return mSliderItems.size();
}
static class SliderAdapterVH extends ViewHolder {
DrawerSliderBinding binding;
SliderAdapterVH(DrawerSliderBinding itemView) {
super(itemView.getRoot());
binding = itemView;
}
}
}

@ -71,6 +71,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
@ -87,6 +88,8 @@ import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
import com.github.stom79.mytransl.MyTransL;
import com.smarteist.autoimageslider.SliderAnimations;
import com.smarteist.autoimageslider.SliderView;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.EmojiPopup;
import com.vanniktech.emoji.one.EmojiOneProvider;
@ -128,6 +131,7 @@ import app.fedilab.android.databinding.DrawerStatusFilteredBinding;
import app.fedilab.android.databinding.DrawerStatusFilteredHideBinding;
import app.fedilab.android.databinding.DrawerStatusHiddenBinding;
import app.fedilab.android.databinding.DrawerStatusNotificationBinding;
import app.fedilab.android.databinding.DrawerStatusPixelfedBinding;
import app.fedilab.android.databinding.DrawerStatusReportBinding;
import app.fedilab.android.databinding.LayoutMediaBinding;
import app.fedilab.android.databinding.LayoutPollItemBinding;
@ -157,6 +161,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public static final int STATUS_ART = 2;
public static final int STATUS_FILTERED = 3;
public static final int STATUS_FILTERED_HIDE = 4;
public static final int STATUS_PIXELFED = 5;
private final List<Status> statusList;
private final boolean minified;
private final Timeline.TimeLineEnum timelineType;
@ -164,6 +169,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
private final boolean checkRemotely;
public FetchMoreCallBack fetchMoreCallBack;
private Context context;
private boolean visiblePixelfed;
private RecyclerView mRecyclerView;
@ -188,6 +194,14 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
return -1;
}
private static boolean isVisiblePixelfed(Status status) {
if (status.reblog != null) {
status = status.reblog;
}
return status.media_attachments != null && status.media_attachments.size() > 0;
}
private static boolean isVisible(Timeline.TimeLineEnum timelineType, Status status) {
if (timelineType == Timeline.TimeLineEnum.HOME && !show_boosts && status.reblog != null) {
return false;
@ -392,6 +406,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
boolean confirmFav = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION_FAV), false);
boolean confirmBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_VALIDATION), true);
boolean fullAttachement = sharedpreferences.getBoolean(context.getString(R.string.SET_FULL_PREVIEW), false);
boolean expand_media = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_MEDIA), false);
boolean displayBookmark = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_BOOKMARK) + MainActivity.currentUserID + MainActivity.currentInstance, true);
boolean displayTranslate = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_TRANSLATE) + MainActivity.currentUserID + MainActivity.currentInstance, false);
boolean displayCounters = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_COUNTER_FAV_BOOST), false);
@ -399,6 +414,14 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
boolean extraFeatures = sharedpreferences.getBoolean(context.getString(R.string.SET_EXTAND_EXTRA_FEATURES) + MainActivity.currentUserID + MainActivity.currentInstance, false);
boolean displayQuote = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_QUOTES) + MainActivity.currentUserID + MainActivity.currentInstance, true);
boolean displayReactions = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_REACTIONS) + MainActivity.currentUserID + MainActivity.currentInstance, true);
boolean compactButtons = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_COMPACT_ACTION_BUTTON), false);
if (compactButtons) {
ConstraintSet set = new ConstraintSet();
set.clone(holder.binding.actionButtons);
set.clear(R.id.status_emoji, ConstraintSet.END);
set.applyTo(holder.binding.actionButtons);
}
if (removeLeftMargin) {
LinearLayoutCompat.MarginLayoutParams p = (LinearLayoutCompat.MarginLayoutParams) holder.binding.spoiler.getLayoutParams();
@ -1053,6 +1076,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
ressource = R.drawable.ic_baseline_mail_24;
break;
}
if (statusToDeal.local_only) {
holder.binding.localOnly.setVisibility(View.VISIBLE);
} else {
holder.binding.localOnly.setVisibility(View.GONE);
}
if (status.isFocused) {
holder.binding.statusInfo.setVisibility(View.VISIBLE);
holder.binding.reblogsCount.setText(String.valueOf(status.reblogs_count));
@ -1271,7 +1301,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
boolean singleMedia = statusToDeal.media_attachments.size() == 1;
for (Attachment attachment : statusToDeal.media_attachments) {
LayoutMediaBinding layoutMediaBinding = LayoutMediaBinding.inflate(LayoutInflater.from(context));
if (fullAttachement && !statusToDeal.sensitive) {
if (fullAttachement && (!statusToDeal.sensitive || expand_media)) {
float ratio = 1.0f;
float mediaH = -1.0f;
@ -1310,7 +1340,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
loadAndAddAttachment(context, layoutMediaBinding, holder, adapter, mediaPosition, -1.f, -1.f, -1.f, statusToDeal, attachment, singleMedia);
}
mediaPosition++;
if ((fullAttachement && !statusToDeal.sensitive) || singleMedia) {
if ((fullAttachement && (!statusToDeal.sensitive || expand_media)) || singleMedia) {
holder.binding.mediaContainer.addView(layoutMediaBinding.getRoot());
} else {
holder.binding.attachmentsList.addView(layoutMediaBinding.getRoot());
@ -1713,6 +1743,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
statusDraft.statusReplyList = new ArrayList<>();
statusToDeal.text = statusSource.text;
statusToDeal.spoiler_text = statusSource.spoiler_text;
if (statusToDeal.spoiler_text != null && statusToDeal.spoiler_text.length() > 0) {
statusToDeal.spoilerChecked = true;
}
statusDraft.statusDraftList.add(statusToDeal);
intent.putExtra(Helper.ARG_STATUS_DRAFT, statusDraft);
intent.putExtra(Helper.ARG_EDIT_STATUS_ID, statusToDeal.id);
@ -2023,7 +2056,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
drawerFetchMoreBinding.fetchMoreMin.setOnClickListener(v -> {
status.isFetchMore = false;
int position = holder.getBindingAdapterPosition();
int position2 = getStatusPosition(statusList, status);
adapter.notifyItemChanged(position);
if (position < statusList.size() - 1) {
String fromId;
@ -2093,7 +2125,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
boolean expand_media = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_MEDIA), false);
LinearLayout.LayoutParams lp;
if (fullAttachement && mediaH > 0 && !statusToDeal.sensitive) {
if (fullAttachement && mediaH > 0 && (!statusToDeal.sensitive || expand_media)) {
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, (int) (mediaH * ratio));
layoutMediaBinding.media.setScaleType(ImageView.ScaleType.FIT_CENTER);
} else {
@ -2207,7 +2239,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
});
if (!statusToDeal.sensitive && (fullAttachement || singleImage)) {
if ((!statusToDeal.sensitive || expand_media) && (fullAttachement || singleImage)) {
layoutMediaBinding.getRoot().setPadding(0, 0, 0, 10);
} else {
layoutMediaBinding.getRoot().setPadding(0, 0, 10, 0);
@ -2215,28 +2247,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
/* private static boolean mediaObfuscated(Status status) {
//Media is not sensitive and doesn't have a spoiler text
if (!status.isMediaObfuscated) {
return false;
}
if (!status.sensitive && (status.spoiler_text == null || status.spoiler_text.trim().isEmpty())) {
return false;
}
if (status.isMediaObfuscated && status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
return true;
} else {
return status.sensitive;
}
}*/
/**
* Send a broadcast to other open fragments that content a timeline
*
@ -2261,68 +2271,20 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
LocalBroadcastManager.getInstance(context).sendBroadcast(intentBC);
}
@Override
public int getItemViewType(int position) {
if (timelineType == Timeline.TimeLineEnum.ART) {
return STATUS_ART;
} else {
if (statusList.get(position).filteredByApp != null) {
if (statusList.get(position).filteredByApp.filter_action.equals("warn")) {
return STATUS_FILTERED;
} else { //These messages should not be displayed unless they contain a fetch more button
if (!statusList.get(position).isFetchMore) {
return STATUS_HIDDEN;
} else {
return STATUS_FILTERED_HIDE;
}
}
} else {
return isVisible(timelineType, statusList.get(position)) ? STATUS_VISIBLE : STATUS_HIDDEN;
}
/* private static boolean mediaObfuscated(Status status) {
//Media is not sensitive and doesn't have a spoiler text
if (!status.isMediaObfuscated) {
return false;
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
if (viewType == STATUS_HIDDEN) { //Hidden statuses - ie: filtered
DrawerStatusHiddenBinding itemBinding = DrawerStatusHiddenBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_ART) { //Art statuses
DrawerStatusArtBinding itemBinding = DrawerStatusArtBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_FILTERED) { //Filtered warn
DrawerStatusFilteredBinding itemBinding = DrawerStatusFilteredBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_FILTERED_HIDE) { //Filtered hide
DrawerStatusFilteredHideBinding itemBinding = DrawerStatusFilteredHideBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else { //Classic statuses
if (!minified) {
DrawerStatusBinding itemBinding = DrawerStatusBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else {
DrawerStatusReportBinding itemBinding = DrawerStatusReportBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
}
if (!status.sensitive && (status.spoiler_text == null || status.spoiler_text.trim().isEmpty())) {
return false;
}
}
public int getCount() {
return statusList.size();
}
public Status getItem(int position) {
return statusList.get(position);
}
public long getItemId(int position) {
return position;
}
if (status.isMediaObfuscated && status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
return true;
} else {
return status.sensitive;
}
}*/
public static void applyColor(Context context, StatusViewHolder holder) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
@ -2410,6 +2372,87 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@Override
public int getItemViewType(int position) {
if (timelineType == Timeline.TimeLineEnum.ART) {
return STATUS_ART;
} else {
if (statusList.get(position).filteredByApp != null) {
if (statusList.get(position).filteredByApp.filter_action.equals("warn")) {
return STATUS_FILTERED;
} else { //These messages should not be displayed unless they contain a fetch more button
if (!statusList.get(position).isFetchMore) {
return STATUS_HIDDEN;
} else {
return STATUS_FILTERED_HIDE;
}
}
} else {
if (isVisible(timelineType, statusList.get(position))) {
if (visiblePixelfed && isVisiblePixelfed(statusList.get(position)) && timelineType != Timeline.TimeLineEnum.UNKNOWN) {
return STATUS_PIXELFED;
} else {
return STATUS_VISIBLE;
}
} else {
return STATUS_HIDDEN;
}
}
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
visiblePixelfed = sharedpreferences.getBoolean(context.getString(R.string.SET_PIXELFED_PRESENTATION) + MainActivity.currentUserID + MainActivity.currentInstance, false);
if (viewType == STATUS_HIDDEN) { //Hidden statuses - ie: filtered
DrawerStatusHiddenBinding itemBinding = DrawerStatusHiddenBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_ART) { //Art statuses
DrawerStatusArtBinding itemBinding = DrawerStatusArtBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_PIXELFED) { //Art statuses
DrawerStatusPixelfedBinding itemBinding = DrawerStatusPixelfedBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_FILTERED) { //Filtered warn
DrawerStatusFilteredBinding itemBinding = DrawerStatusFilteredBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else if (viewType == STATUS_FILTERED_HIDE) { //Filtered hide
DrawerStatusFilteredHideBinding itemBinding = DrawerStatusFilteredHideBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else { //Classic statuses
if (!minified) {
DrawerStatusBinding itemBinding = DrawerStatusBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
} else {
DrawerStatusReportBinding itemBinding = DrawerStatusReportBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new StatusViewHolder(itemBinding);
}
}
}
public int getCount() {
return statusList.size();
}
public Status getItem(int position) {
return statusList.get(position);
}
public long getItemId(int position) {
return position;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
//Nothing to do with hidden statuses
@ -2577,6 +2620,44 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
intent.putExtra(Helper.ARG_STATUS, status);
context.startActivity(intent);
});
} else if (viewHolder.getItemViewType() == STATUS_PIXELFED) {
Status statusToDeal = status.reblog != null ? status.reblog : status;
StatusViewHolder holder = (StatusViewHolder) viewHolder;
if (status.reblog != null) {
MastodonHelper.loadPPMastodon(holder.bindingPixelfed.artReblogPp, status.account);
holder.bindingPixelfed.artReblogPp.setVisibility(View.VISIBLE);
} else {
holder.bindingPixelfed.artReblogPp.setVisibility(View.GONE);
}
MastodonHelper.loadPPMastodon(holder.bindingPixelfed.artPp, statusToDeal.account);
SliderAdapter adapter = new SliderAdapter(statusToDeal);
holder.bindingPixelfed.artMedia.setSliderAdapter(adapter);
holder.bindingPixelfed.artMedia.setSliderTransformAnimation(SliderAnimations.SIMPLETRANSFORMATION);
holder.bindingPixelfed.artMedia.setAutoCycleDirection(SliderView.AUTO_CYCLE_DIRECTION_BACK_AND_FORTH);
holder.bindingPixelfed.artMedia.setScrollTimeInSec(4);
holder.bindingPixelfed.artMedia.startAutoCycle();
holder.bindingPixelfed.commentNumber.setText(String.valueOf(statusToDeal.replies_count));
holder.bindingPixelfed.artUsername.setText(
statusToDeal.account.getSpanDisplayName(context,
new WeakReference<>(holder.bindingPixelfed.artUsername)),
TextView.BufferType.SPANNABLE);
holder.bindingPixelfed.artAcct.setText(String.format(Locale.getDefault(), "@%s", statusToDeal.account.acct));
holder.bindingPixelfed.artPp.setOnClickListener(v -> {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, statusToDeal.account);
intent.putExtras(b);
ActivityOptionsCompat options = ActivityOptionsCompat
.makeSceneTransitionAnimation((Activity) context, holder.bindingPixelfed.artPp, context.getString(R.string.activity_porfile_pp));
context.startActivity(intent, options.toBundle());
});
holder.bindingPixelfed.bottomBanner.setOnClickListener(v -> {
Intent intent = new Intent(context, ContextActivity.class);
intent.putExtra(Helper.ARG_STATUS, statusToDeal);
context.startActivity(intent);
});
}
}
@ -2602,8 +2683,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
DrawerStatusReportBinding bindingReport;
DrawerStatusNotificationBinding bindingNotification;
DrawerStatusArtBinding bindingArt;
DrawerStatusPixelfedBinding bindingPixelfed;
DrawerStatusFilteredBinding bindingFiltered;
DrawerStatusFilteredHideBinding bindingFilteredHide;
StatusViewHolder(DrawerStatusBinding itemView) {
super(itemView.getRoot());
binding = itemView;
@ -2632,6 +2715,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
bindingArt = itemView;
}
StatusViewHolder(DrawerStatusPixelfedBinding itemView) {
super(itemView.getRoot());
bindingPixelfed = itemView;
}
StatusViewHolder(DrawerStatusFilteredBinding itemView) {
super(itemView.getRoot());
bindingFiltered = itemView;

@ -200,5 +200,4 @@ public class FragmentAdminReport extends Fragment {
}
}

@ -68,11 +68,11 @@ import es.dmoral.toasty.Toasty;
public class FragmentLoginMain extends Fragment {
private static final int REQUEST_CODE = 5412;
private final int PICK_IMPORT = 5557;
private FragmentLoginMainBinding binding;
private boolean searchInstanceRunning = false;
private String oldSearch;
private static final int REQUEST_CODE = 5412;
private final int PICK_IMPORT = 5557;
private ActivityResultLauncher<String> permissionLauncher;
public View onCreateView(@NonNull LayoutInflater inflater,

@ -46,6 +46,8 @@ import es.dmoral.toasty.Toasty;
public class FragmentMediaProfile extends Fragment {
String tempToken;
String tempInstance;
private FragmentPaginationBinding binding;
private AccountsVM accountsVM;
private Account accountTimeline;
@ -53,8 +55,6 @@ public class FragmentMediaProfile extends Fragment {
private List<Status> mediaStatuses;
private String max_id;
private ImageAdapter imageAdapter;
String tempToken;
String tempInstance;
private boolean checkRemotely;
private String accountId;

@ -76,6 +76,13 @@ public class FragmentExtraFeaturesSettings extends PreferenceFragmentCompat impl
String format = sharedpreferences.getString(getString(R.string.SET_POST_FORMAT) + MainActivity.currentUserID + MainActivity.currentInstance, "text/plain");
SET_POST_FORMAT.setValue(format);
}
ListPreference SET_COMPOSE_LOCAL_ONLY = findPreference(getString(R.string.SET_COMPOSE_LOCAL_ONLY));
if (SET_COMPOSE_LOCAL_ONLY != null) {
SET_COMPOSE_LOCAL_ONLY.getContext().setTheme(Helper.dialogStyle());
int localOnly = sharedpreferences.getInt(getString(R.string.SET_COMPOSE_LOCAL_ONLY) + MainActivity.currentUserID + MainActivity.currentInstance, 0);
SET_COMPOSE_LOCAL_ONLY.setValue(String.valueOf(localOnly));
}
}
@Override
@ -119,6 +126,12 @@ public class FragmentExtraFeaturesSettings extends PreferenceFragmentCompat impl
editor.putString(getString(R.string.SET_POST_FORMAT) + MainActivity.currentUserID + MainActivity.currentInstance, SET_POST_FORMAT.getValue());
}
}
if (key.compareToIgnoreCase(getString(R.string.SET_COMPOSE_LOCAL_ONLY)) == 0) {
ListPreference SET_COMPOSE_LOCAL_ONLY = findPreference(getString(R.string.SET_COMPOSE_LOCAL_ONLY));
if (SET_COMPOSE_LOCAL_ONLY != null) {
editor.putInt(getString(R.string.SET_COMPOSE_LOCAL_ONLY) + MainActivity.currentUserID + MainActivity.currentInstance, Integer.parseInt(SET_COMPOSE_LOCAL_ONLY.getValue()));
}
}
editor.apply();
}
}

@ -32,7 +32,6 @@ import es.dmoral.toasty.Toasty;
public class FragmentThemingSettings extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreatePreferences(Bundle bundle, String s) {
createPref();

@ -73,6 +73,12 @@ public class FragmentTimelinesSettings extends PreferenceFragmentCompat implemen
boolean checked = sharedpreferences.getBoolean(getString(R.string.SET_DISPLAY_TRANSLATE) + MainActivity.currentUserID + MainActivity.currentInstance, false);
SET_DISPLAY_TRANSLATE.setChecked(checked);
}
SwitchPreferenceCompat SET_PIXELFED_PRESENTATION = findPreference(getString(R.string.SET_PIXELFED_PRESENTATION));
if (SET_PIXELFED_PRESENTATION != null) {
boolean checked = sharedpreferences.getBoolean(getString(R.string.SET_PIXELFED_PRESENTATION) + MainActivity.currentUserID + MainActivity.currentInstance, false);
SET_PIXELFED_PRESENTATION.setChecked(checked);
}
}
@Override
@ -95,6 +101,12 @@ public class FragmentTimelinesSettings extends PreferenceFragmentCompat implemen
editor.putBoolean(getString(R.string.SET_DISPLAY_TRANSLATE) + MainActivity.currentUserID + MainActivity.currentInstance, SET_DISPLAY_TRANSLATE.isChecked());
}
}
if (key.compareToIgnoreCase(getString(R.string.SET_PIXELFED_PRESENTATION)) == 0) {
SwitchPreferenceCompat SET_PIXELFED_PRESENTATION = findPreference(getString(R.string.SET_PIXELFED_PRESENTATION));
if (SET_PIXELFED_PRESENTATION != null) {
editor.putBoolean(getString(R.string.SET_PIXELFED_PRESENTATION) + MainActivity.currentUserID + MainActivity.currentInstance, SET_PIXELFED_PRESENTATION.isChecked());
}
}
editor.apply();
}
}

@ -50,12 +50,11 @@ import app.fedilab.android.viewmodel.mastodon.StatusesVM;
public class FragmentMastodonContext extends Fragment {
public FirstMessage firstMessage;
private FragmentPaginationBinding binding;
private StatusesVM statusesVM;
private List<Status> statuses;
private StatusAdapter statusAdapter;
public FirstMessage firstMessage;
//Handle actions that can be done in other fragments
private final BroadcastReceiver receive_action = new BroadcastReceiver() {
@Override

@ -209,6 +209,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
excludeType.add("status");
excludeType.add("admin.sign_up");
excludeType.add("admin.report");
excludeType.add("pleroma:emoji_reaction");
if (notificationType == NotificationTypeEnum.ALL) {
aggregateNotification = sharedpreferences.getBoolean(getString(R.string.SET_AGGREGATE_NOTIFICATION), true);
if (excludedCategories != null) {

@ -16,7 +16,6 @@ package app.fedilab.android.ui.fragment.timeline;
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentUserID;
import static app.fedilab.android.BaseMainActivity.networkAvailable;
import android.content.BroadcastReceiver;
@ -52,6 +51,7 @@ import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Pagination;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.api.Statuses;
import app.fedilab.android.client.entities.app.BubbleTimeline;
import app.fedilab.android.client.entities.app.PinnedTimeline;
import app.fedilab.android.client.entities.app.RemoteInstance;
import app.fedilab.android.client.entities.app.StatusCache;
@ -84,9 +84,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
private StatusAdapter statusAdapter;
private Timeline.TimeLineEnum timelineType;
private List<Status> timelineStatuses;
private boolean checkRemotely;
private String accountIDInRemoteInstance;
//Handle actions that can be done in other fragments
private final BroadcastReceiver receive_action = new BroadcastReceiver() {
@Override
@ -103,12 +100,24 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
if (receivedStatus != null && statusAdapter != null) {
int position = getPosition(receivedStatus);
if (position >= 0) {
timelineStatuses.get(position).reblog = receivedStatus.reblog;
timelineStatuses.get(position).reblogged = receivedStatus.reblogged;
timelineStatuses.get(position).favourited = receivedStatus.favourited;
timelineStatuses.get(position).bookmarked = receivedStatus.bookmarked;
timelineStatuses.get(position).reblogs_count = receivedStatus.reblogs_count;
timelineStatuses.get(position).favourites_count = receivedStatus.favourites_count;
if (receivedStatus.reblog != null) {
timelineStatuses.get(position).reblog = receivedStatus.reblog;
}
if (timelineStatuses.get(position).reblog != null) {
timelineStatuses.get(position).reblog.reblogged = receivedStatus.reblogged;
timelineStatuses.get(position).reblog.favourited = receivedStatus.favourited;
timelineStatuses.get(position).reblog.bookmarked = receivedStatus.bookmarked;
timelineStatuses.get(position).reblog.reblogs_count = receivedStatus.reblogs_count;
timelineStatuses.get(position).reblog.favourites_count = receivedStatus.favourites_count;
} else {
timelineStatuses.get(position).reblogged = receivedStatus.reblogged;
timelineStatuses.get(position).favourited = receivedStatus.favourited;
timelineStatuses.get(position).bookmarked = receivedStatus.bookmarked;
timelineStatuses.get(position).reblogs_count = receivedStatus.reblogs_count;
timelineStatuses.get(position).favourites_count = receivedStatus.favourites_count;
}
statusAdapter.notifyItemChanged(position);
}
} else if (delete_statuses_for_user != null && statusAdapter != null) {
@ -162,10 +171,13 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
}
}
};
private boolean checkRemotely;
private String accountIDInRemoteInstance;
private boolean isViewInitialized;
private Statuses initialStatuses;
private String list_id;
private TagTimeline tagTimeline;
private BubbleTimeline bubbleTimeline;
private LinearLayoutManager mLayoutManager;
private Account accountTimeline;
private boolean exclude_replies, exclude_reblogs, show_pinned, media_only, minified;
@ -179,6 +191,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
private int lockForResumeCall;
private boolean isNotPinnedTimeline;
private int extraCalls;
//Allow to recreate data when detaching/attaching fragment
public void recreate() {
initialStatuses = null;
@ -241,7 +254,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
return -1;
}
for (Status _status : timelineStatuses) {
if (_status.id != null && _status.id.compareTo(status.id) == 0) {
if (_status.reblog == null && _status.id != null && _status.id.compareTo(status.id) == 0) {
found = true;
break;
} else if (_status.reblog != null && _status.reblog.id != null && _status.reblog.id.compareTo(status.id) == 0) {
found = true;
break;
}
@ -332,6 +348,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
isViewInitialized = getArguments().getBoolean(Helper.ARG_INITIALIZE_VIEW, true);
isNotPinnedTimeline = isViewInitialized;
tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE);
bubbleTimeline = (BubbleTimeline) getArguments().getSerializable(Helper.ARG_BUBBLE_TIMELINE);
accountTimeline = (Account) getArguments().getSerializable(Helper.ARG_ACCOUNT);
exclude_replies = !getArguments().getBoolean(Helper.ARG_SHOW_REPLIES, true);
checkRemotely = getArguments().getBoolean(Helper.ARG_CHECK_REMOTELY, false);
@ -342,18 +359,26 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
minified = getArguments().getBoolean(Helper.ARG_MINIFIED, false);
statusReport = (Status) getArguments().getSerializable(Helper.ARG_STATUS_REPORT);
}
//When visiting a profile without being authenticated
if (checkRemotely) {
String[] acctArray = accountTimeline.acct.split("@");
if (acctArray.length > 1) {
remoteInstance = acctArray[1];
}
if (remoteInstance != null && remoteInstance.equalsIgnoreCase(currentInstance)) {
checkRemotely = false;
} else if (remoteInstance == null) {
checkRemotely = false;
}
}
if (tagTimeline != null) {
ident = "@T@" + tagTimeline.name;
if (tagTimeline.isART) {
timelineType = Timeline.TimeLineEnum.ART;
}
} else if (bubbleTimeline != null) {
ident = "@B@Bubble";
} else if (list_id != null) {
ident = "@l@" + list_id;
} else if (remoteInstance != null && !checkRemotely) {
@ -387,7 +412,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
binding.swipeContainer.setRefreshing(false);
binding.loadingNextElements.setVisibility(View.GONE);
flagLoading = false;
int currentPosition = mLayoutManager.findFirstVisibleItemPosition();
if (timelineStatuses != null && fetched_statuses != null && fetched_statuses.statuses != null && fetched_statuses.statuses.size() > 0) {
try {
if (statusToUpdate != null) {
@ -450,7 +475,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
update.onUpdate(0, timelineType, slug);
}
if (direction == DIRECTION.TOP && fetchingMissing) {
binding.recyclerView.scrollToPosition(getPosition(fetched_statuses.statuses.get(fetched_statuses.statuses.size() - 1)) + 1);
int newPosition = currentPosition + fetched_statuses.statuses.size() + 1;
if (newPosition < timelineStatuses.size()) {
binding.recyclerView.scrollToPosition(newPosition);
}
}
if (!fetchingMissing) {
if (fetched_statuses.pagination.max_id == null) {
@ -619,7 +647,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
}
});
//For first tab we fetch new messages, if we keep position
if (slug != null && slug.compareTo(Helper.getSlugOfFirstFragment(requireActivity(), currentUserID, currentInstance)) == 0 && rememberPosition) {
if (slug != null /*&& slug.compareTo(Helper.getSlugOfFirstFragment(requireActivity(), currentUserID, currentInstance)) == 0*/ && rememberPosition) {
route(DIRECTION.FETCH_NEW, true);
}
}
@ -712,6 +740,14 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
timelineParams.local = false;
timelineParams.remote = true;
break;
case BUBBLE:
if (bubbleTimeline != null) {
timelineParams.onlyMedia = bubbleTimeline.only_media;
timelineParams.remote = bubbleTimeline.remote;
timelineParams.replyVisibility = bubbleTimeline.reply_visibility;
timelineParams.excludeVisibilities = bubbleTimeline.exclude_visibilities;
}
break;
case LIST:
timelineParams.listId = list_id;
break;
@ -898,6 +934,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
routeCommon(direction, fetchingMissing, statusToUpdate);
} else if (timelineType == Timeline.TimeLineEnum.PUBLIC) { //PUBLIC TIMELINE
routeCommon(direction, fetchingMissing, statusToUpdate);
} else if (timelineType == Timeline.TimeLineEnum.BUBBLE) { //BUBBLE TIMELINE
routeCommon(direction, fetchingMissing, statusToUpdate);
} else if (timelineType == Timeline.TimeLineEnum.REMOTE) { //REMOTE TIMELINE
//NITTER TIMELINES
if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) {

@ -36,6 +36,7 @@ public class FragmentProfileTimeline extends Fragment {
private Account account;
private FragmentProfileTimelinesBinding binding;
private boolean checkRemotely;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {

@ -137,6 +137,8 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter {
bundle.putSerializable(Helper.ARG_TAG_TIMELINE, pinnedTimeline.tagTimeline);
} else if (pinnedTimeline.type == Timeline.TimeLineEnum.REMOTE) {
bundle.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline);
} else if (pinnedTimeline.type == Timeline.TimeLineEnum.BUBBLE) {
bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, pinnedTimeline.bubbleTimeline);
}
}

@ -30,8 +30,8 @@ import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
public class FedilabProfilePageAdapter extends FragmentStatePagerAdapter {
private final Account account;
private Fragment mCurrentFragment;
private final boolean checkRemotely;
private Fragment mCurrentFragment;
public FedilabProfilePageAdapter(FragmentManager fm, Account account, boolean remotely) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);

@ -30,8 +30,8 @@ import app.fedilab.android.ui.fragment.timeline.FragmentProfileTimeline;
public class FedilabProfileTLPageAdapter extends FragmentStatePagerAdapter {
private final Account account;
private Fragment mCurrentFragment;
private final boolean checkRemotely;
private Fragment mCurrentFragment;
public FedilabProfileTLPageAdapter(FragmentManager fm, Account account, boolean remotely) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);

@ -304,12 +304,17 @@ public class NotificationsVM extends AndroidViewModel {
boolean favourite,
boolean reblog,
boolean mention,
boolean poll) {
boolean poll,
boolean status,
boolean updates,
boolean signup,
boolean report
) {
pushSubscriptionMutableLiveData = new MutableLiveData<>();
MastodonNotificationsService mastodonNotificationsService = init(instance);
new Thread(() -> {
PushSubscription pushSubscription = null;
Call<PushSubscription> pushSubscriptionCall = mastodonNotificationsService.pushSubscription(token, endpoint, keys_p256dh, keys_auth, follow, favourite, reblog, mention, poll);
Call<PushSubscription> pushSubscriptionCall = mastodonNotificationsService.pushSubscription(token, endpoint, keys_p256dh, keys_auth, follow, favourite, reblog, mention, poll, status, updates, signup, report);
if (pushSubscriptionCall != null) {
try {
Response<PushSubscription> pushSubscriptionResponse = pushSubscriptionCall.execute();

@ -417,6 +417,9 @@ public class TimelinesVM extends AndroidViewModel {
case PUBLIC:
timelineCall = mastodonTimelinesService.getPublic(timelineParams.token, false, true, timelineParams.onlyMedia, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit);
break;
case BUBBLE:
timelineCall = mastodonTimelinesService.getBubble(timelineParams.token, timelineParams.onlyMedia, timelineParams.remote, timelineParams.withMuted, timelineParams.excludeVisibilities, timelineParams.replyVisibility, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit);
break;
case ART:
case TAG:
timelineCall = mastodonTimelinesService.getHashTag(timelineParams.token, timelineParams.hashtagTrim, timelineParams.local, timelineParams.onlyMedia, timelineParams.all, timelineParams.any, timelineParams.none, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit);
@ -949,6 +952,7 @@ public class TimelinesVM extends AndroidViewModel {
public String userId;
public Boolean remote;
public Boolean onlyMedia;
public Boolean withMuted;
public String hashtagTrim;
public List<String> all;
public List<String> any;
@ -961,6 +965,8 @@ public class TimelinesVM extends AndroidViewModel {
public int limit = 40;
public Boolean local;
public List<String> excludeType;
public List<String> excludeVisibilities;
public String replyVisibility;
public TimelineParams(@NonNull Timeline.TimeLineEnum timeLineEnum, @Nullable FragmentMastodonTimeline.DIRECTION timelineDirection, @Nullable String ident) {
if (type != Timeline.TimeLineEnum.REMOTE) {

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" />
<path
android:fillColor="@android:color/white"
android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0" />
<path
android:fillColor="@android:color/white"
android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0" />
</vector>

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
</vector>

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/fab_margin">
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/value_public"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/v_public"
android:textSize="16sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/value_unlisted"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/v_unlisted"
android:textSize="16sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/value_local"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/local"
android:textSize="16sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/value_private"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/v_private"
android:textSize="16sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/value_direct"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/v_direct"
android:textSize="16sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/value_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
android:text="@string/v_list"
android:textSize="16sp" />
</androidx.appcompat.widget.LinearLayoutCompat>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/fab_margin">
<RadioGroup
android:id="@+id/reply_visibility"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/all"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/all"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/following"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/following"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/self"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/self"
android:textSize="16sp" />
</RadioGroup>
</androidx.appcompat.widget.LinearLayoutCompat>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.AppCompatImageView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/iv_auto_image_slider"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" />

@ -159,6 +159,14 @@
android:singleLine="true"
tools:text="@tools:sample/full_names" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/local_only"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/local_only"
android:src="@drawable/ic_baseline_local_only_24" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/visibility_small"
android:layout_width="20dp"

@ -65,7 +65,7 @@
android:focusable="true"
android:gravity="top|start"
android:inputType="textMultiLine|textCapSentences"
android:minLines="6"
android:minLines="8"
app:layout_constraintEnd_toStartOf="@id/button_emoji"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/content_spoiler" />
@ -103,6 +103,17 @@
app:layout_constraintTop_toBottomOf="@id/button_emoji_one"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_local_only"
style="@style/Fedilab.SmallIconButton"
android:layout_marginEnd="6dp"
android:contentDescription="@string/local_only"
android:visibility="gone"
app:icon="@drawable/ic_baseline_remove_red_eye_24"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_text_format"
tools:visibility="visible" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/sensitive_media"
android:layout_width="0dp"

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2022 Thomas Schneider
This file is a part of Fedilab
This program is free software; you can redistribute it and/or modify it under the terms of the
GNU General Public License as published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
Public License for more details.
You should have received a copy of the GNU General Public License along with Fedilab; if not,
see <http://www.gnu.org/licenses>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/art_container"
android:layout_marginHorizontal="@dimen/card_margin"
android:layout_marginTop="@dimen/card_margin"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.smarteist.autoimageslider.SliderView
android:id="@+id/art_media"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:sliderAnimationDuration="200"
app:sliderAutoCycleDirection="back_and_forth"
app:sliderAutoCycleEnabled="true"
app:sliderIndicatorAnimationDuration="200"
app:sliderIndicatorGravity="center_horizontal|top"
app:sliderIndicatorMargin="15dp"
app:sliderIndicatorOrientation="horizontal"
app:sliderIndicatorPadding="3dp"
app:sliderIndicatorRadius="2dp"
app:sliderIndicatorSelectedColor="?colorPrimary"
app:sliderIndicatorUnselectedColor="?colorControlNormal"
app:sliderScrollTimeInSec="1"
app:sliderStartAutoCycle="true" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bottom_banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#44000000"
android:orientation="horizontal"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="@+id/art_media"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<RelativeLayout
android:layout_width="50dp"
android:layout_height="50dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/art_pp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/art_reblog_pp"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true" />
</RelativeLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/art_author"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="1"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/art_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/art_acct"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/comment_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:drawableStart="@drawable/ic_baseline_chat_bubble_24"
android:drawablePadding="5dp"
android:textSize="16sp"
tools:text="23" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_show_media_only"
android:checkable="true"
android:title="@string/show_media_only"
app:actionViewClass="android.widget.CheckBox"
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/action_remote"
android:checkable="true"
android:title="@string/remote"
app:actionViewClass="android.widget.CheckBox"
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/action_exclude_visibility"
android:title="@string/exclude_visibility"
app:showAsAction="always" />
<item
android:id="@+id/action_reply_visibility"
android:title="@string/reply_visibility"
app:showAsAction="always" />
</menu>

@ -951,4 +951,24 @@
<string name="api_key">Klíč pro API překladače</string>
<string name="version">Verze</string>
<string name="set_translator_version">Verze překladače</string>
<string name="icons_visibility">Viditelnost ikon</string>
<string name="icons_visibility_summary">Můžete bezpečně skrýt tyto ikony v dolní oblasti, abyste získali více prostoru. Jsou k dispozici i v podnabídce.</string>
<string name="post_format">Formát příspěvků</string>
<string name="icons_extra_features">Ikony pro extra funkce</string>
<string name="icons_extra_features_visibility_summary">Pokud vaše instance nepodporuje některé extra funkce, můžete tyto ikony skýt</string>
<string name="set_display_quote_indication">Zobrazit tlačítko „Citovat“</string>
<string name="set_display_reaction_indication">Zobrazit tlačítko „Reakce“</string>
<string name="set_extand_extra_features">Povolením této volby se zobrazí extra funkce. Jsou určeny pro softwary sociálních sítí jako Pleroma, Akkoma nebo Glitch Social</string>
<string name="set_extand_extra_features_title">Extra funkce</string>
<string name="set_post_format">Formát příspěvků</string>
<string name="bubble">Bublina</string>
<string name="set_remote_profile">K získání všech zpráv bude aplikace veřejně zobrazovat profily. Interakce budou k federaci zpráv potřebovat dodatečný krok.</string>
<string name="v_list">Seznam</string>
<string name="set_remote_profile_title">Vzdálené profily</string>
<string name="set_display_local_only">Zobrazit tlačítko „Jen místní“</string>
<string name="local_only">Jen místní</string>
<string name="email_status">Stav e-mailu</string>
<string name="login_status">Stav přihlášení</string>
<string name="joined">Připojil(a) se</string>
<string name="silenced">Ztišen(a)</string>
</resources>

@ -41,7 +41,7 @@
<string name="username">Benutzername</string>
<string name="drafts">Entwürfe</string>
<string name="favourite">Favoriten</string>
<string name="follow">Neue Folgende</string>
<string name="follow">Neue Follower</string>
<string name="mention">Erwähnungen</string>
<string name="reblog">Geteilte Beiträge</string>
<string name="show_boosts">Geteilte Beiträge anzeigen</string>
@ -52,30 +52,30 @@
<string name="home_menu">Startseite</string>
<string name="local_menu">Lokale Zeitleiste</string>
<string name="muted_menu">Stummgeschaltete Nutzer</string>
<string name="blocked_menu">Blockierte Nutzer</string>
<string name="blocked_menu">Geblockte Nutzer</string>
<string name="notifications">Benachrichtigungen</string>
<string name="follow_request">Folgeanfragen</string>
<string name="settings">Einstellungen</string>
<string name="send_email">E-Mail senden</string>
<string name="scheduled_toots">Geplante Nachrichten</string>
<string name="scheduled_toots">Geplante Beiträge</string>
<string name="disclaimer_full">Die folgenden Informationen könnten das Profil des Nutzers unvollständig wiedergeben.</string>
<string name="insert_emoji">Emoji einfügen</string>
<string name="no_emoji">Die App verfügt derzeit nicht über benutzerdefinierte Emojis.</string>
<string name="logout_account_confirmation">Bist Du sicher, dass Du Dich von @%1$s@%2$s abmelden möchtest\?</string>
<!-- Status -->
<string name="no_status">Keine Nachrichten zum Anzeigen</string>
<string name="favourite_add">Diese Nachricht Deinen Favoriten hinzufügen\?</string>
<string name="favourite_remove">Diese Nachricht aus Ihren Favoriten entfernen\?</string>
<string name="reblog_add">Diese Nachricht teilen\?</string>
<string name="reblog_remove">Geteilte Nachricht zurückziehen\?</string>
<string name="no_status">Kein Beitrag zum Anzeigen</string>
<string name="favourite_add">Diesen Beitrag Deinen Favoriten hinzufügen\?</string>
<string name="favourite_remove">Diesen Beitrag aus Deinen Favoriten entfernen\?</string>
<string name="reblog_add">Diesen Beitrag teilen\?</string>
<string name="reblog_remove">Geteilten Beitrag zurückziehen\?</string>
<string name="more_action_1">Stummschalten</string>
<string name="more_action_2">Sperren</string>
<string name="more_action_2">Blockieren</string>
<string name="more_action_3">Melden</string>
<string name="more_action_4">Entfernen</string>
<string name="more_action_5">Kopieren</string>
<string name="more_action_6">Teilen</string>
<string name="more_action_7">Erwähnen</string>
<string name="more_action_8">Zeitlich begrenzt stumm schalten</string>
<string name="more_action_8">Zeitlich begrenzt stummschalten</string>
<string name="more_action_9">Löschen &amp; neu entwerfen</string>
<string-array name="more_action_confirm">
<item>Lautlosmodus für dieses Konto aktivieren?</item>
@ -120,11 +120,11 @@
<item quantity="other">%d Tage</item>
</plurals>
<!-- TOOT -->
<string name="toot_select_image_error">Ein Fehler während der Auswahl ist aufgetreten!</string>
<string name="toot_delete_media">Diese Datei löschen?</string>
<string name="toot_error_no_content">Deine Nachricht ist leer!</string>
<string name="toot_sent">Die Nachricht wurde gesendet!</string>
<string name="toot_sensitive">Vertrauliche Inhalte?</string>
<string name="toot_select_image_error">Bei der Medien-Auswahl ist ein Fehler aufgetreten!</string>
<string name="toot_delete_media">Diese Datei entfernen\?</string>
<string name="toot_error_no_content">Dein Beitrag ist leer!</string>
<string name="toot_sent">Der Beitrag wurde gesendet!</string>
<string name="toot_sensitive">Sensibler Inhalt\?</string>
<string name="no_draft">Keine Entwürfe vorhanden!</string>
<string name="choose_accounts">Konto auswählen</string>
<string name="select_accounts">Konten auswählen</string>
@ -141,48 +141,49 @@
<string name="about_thekinrar">Instanzensuche:</string>
<!-- Conversation -->
<!-- Accounts -->
<string name="no_accounts">Keinen Nutzer gefunden</string>
<string name="no_follow_request">Keine Anfragen zu Folgen</string>
<string name="status_cnt">Nachrichten
<string name="no_accounts">Kein Konto zum Anzeigen</string>
<string name="no_follow_request">Keine Folgeanfragen</string>
<string name="status_cnt">Beiträge
\n %1$s</string>
<string name="following_cnt">Folgt \n %1$s</string>
<string name="followers_cnt">Folgende \n %1$s</string>
<string name="followers_cnt">Follower
\n %1$s</string>
<string name="reject">Ablehnen</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">Keine geplanten Nachrichten vorhanden!</string>
<string name="remove_scheduled">Geplante Nachricht löschen\?</string>
<string name="toot_scheduled">Die Nachricht wurde geplant!</string>
<string name="no_scheduled_toots">Keine geplanten Beiträge zum Anzeigen!</string>
<string name="remove_scheduled">Geplanten Beitrag löschen\?</string>
<string name="toot_scheduled">Der Beitrag wurde geplant!</string>
<string name="toot_scheduled_date">Der geplante Termin muss in der Zukunft liegen!</string>
<!-- timed mute -->
<string name="timed_mute_date_error">Die Dauer für die Stummschaltung sollte mehr als eine Minute betragen.</string>
<string name="timed_mute_date">%1$s wurde bis %2$s lautlos geschaltet.
\n Du kannst die Stummschaltung für diese Person beenden, indem Du ihr Profil besuchst.</string>
<string name="timed_mute_profile">%1$s ist bis %2$s stumm geschaltet.
<string name="timed_mute_date">%1$s wurde bis %2$s stummgeschaltet.
\n Du kannst die Stummschaltung für dieses Konto beenden, indem Du die Profilseite besuchst.</string>
<string name="timed_mute_profile">%1$s ist bis %2$s stummgeschaltet.
\n Hier antippen, um die Stummschaltung zu beenden.</string>
<!-- Notifications -->
<string name="no_notifications">Keine Benachrichtigungen</string>
<string name="no_notifications">Keine Benachrichtigung zum Anzeigen</string>
<string name="notif_mention">hat dich erwähnt</string>
<string name="notif_status">hat eine neue Nachricht geschrieben</string>
<string name="notif_status">hat einen neuen Beitrag verfasst</string>
<string name="notif_reblog">hat Deinen Beitrag geteilt</string>
<string name="notif_favourite">hat Deinen Status favorisiert</string>
<string name="notif_follow">folgt dir</string>
<string name="notif_follow_request">fragte, dir zu folgen</string>
<string name="delete_notification_ask_all">Entferne alle Benachrichtigungen?</string>
<string name="notif_follow">folgt Dir</string>
<string name="notif_follow_request">fragt Dir zu folgen</string>
<string name="delete_notification_ask_all">Lösche alle Benachrichtigungen\?</string>
<string name="delete_notification_all">Alle Benachrichtigungen wurden gelöscht!</string>
<!-- HEADER -->
<string name="followers">Folgende</string>
<string name="followers">Follower</string>
<!-- TOAST -->
<string name="client_error">Fehler beim Laden der Client ID!</string>
<string name="toast_block">Konto wurde gesperrt!</string>
<string name="toast_unblock">Das Konto wurde entsperrt!</string>
<string name="toast_mute">Nutzer wurde stumm geschaltet!</string>
<string name="toast_block">Das Konto wurde geblockt!</string>
<string name="toast_unblock">Das Konto wurde entblockt!</string>
<string name="toast_mute">Das Konto wurde stummgeschaltet!</string>
<string name="toast_unmute">Stummschaltung für dieses Konto aufgehoben!</string>
<string name="toast_follow">Du folgst dem Nutzer!</string>
<string name="toast_unfollow">Du folgst dem Nutzer nicht mehr!</string>
<string name="toast_reblog">Die Nachricht wurde geteilt!</string>
<string name="toast_unreblog">Die Nachricht wird nicht länger geteilt!</string>
<string name="toast_favourite">Die Nachricht wurde Deinen Favoriten hinzugefügt!</string>
<string name="toast_unfavourite">Die Nachricht wurde aus Deinen Favoriten entfernt!</string>
<string name="toast_follow">Du folgst dem Konto!</string>
<string name="toast_unfollow">Du folgst dem Konto nicht mehr!</string>
<string name="toast_reblog">Der Beitrag wurde geteilt!</string>
<string name="toast_unreblog">Der Beitrag wird nicht länger geteilt!</string>
<string name="toast_favourite">Der Beitrag wurde Deinen Favoriten hinzugefügt!</string>
<string name="toast_unfavourite">Der Beitrag wurde aus Deinen Favoriten entfernt!</string>
<string name="toast_error">Es ist ein Fehler aufgetreten!</string>
<string name="toast_code_error">Ein Fehler ist aufgetreten! Die Instanz hat keinen Autorisierungscode gesendet!</string>
<string name="toast_error_instance">Der Name der Instanz scheint ungültig zu sein!</string>
@ -191,7 +192,7 @@
<string name="nothing_to_do">Keine Aktion kann durchgeführt werden</string>
<string name="toast_error_translate">Während der Übersetzung ist ein Fehler aufgetreten!</string>
<!-- Settings -->
<string name="set_toots_page">Anzahl der Nachrichten pro Ladevorgang</string>
<string name="set_toots_page">Anzahl der Beiträge pro Ladevorgang</string>
<string name="set_disable_gif">GIF Avatare deaktivieren</string>
<string name="set_notif_follow">Benachrichtigung wenn mir jemand folgt</string>
<string name="set_notif_follow_share">Benachrichtigung wenn jemand meinen Beitrag teilt</string>
@ -199,14 +200,14 @@
<string name="set_notif_follow_mention">Benachrichtigung wenn ich erwähnt werde</string>
<string name="set_notif_follow_poll">Benachrichtigung wenn eine Umfrage beendet ist</string>
<string name="set_notif_status">Benachrichtigung über neue Beiträge</string>
<string name="set_share_validation">Bestätigungsdialog vor dem Teilen anzeigen</string>
<string name="set_share_validation">Bestätigungsdialog vor dem Teilen eines Beitrags anzeigen</string>
<string name="set_share_validation_fav">Bestätigungsdialog vor dem Favorisieren anzeigen</string>
<string name="set_notify">Benachrichtigen?</string>
<string name="set_notif_silent">Leise Benachrichtigungen</string>
<string name="set_nsfw_timeout">NSFW Anzeigedauer (in Sekunden, 0 ≙ deaktiviert)</string>
<string name="set_med_desc_timeout">Timeout der Medienbeschreibung (Sekunden, 0 bedeutet aus)</string>
<string name="set_notif_silent">Stille Benachrichtigungen</string>
<string name="set_nsfw_timeout">Anzeigedauer heikler Inhalte (NSFW) (Sekunden, 0 = deaktiviert)</string>
<string name="set_med_desc_timeout">Timeout der Medien-Beschreibung (Sekunden, 0 = deaktiviert)</string>
<string name="settings_title_custom_sharing">Benutzerdefiniertes Teilen</string>
<string name="settings_custom_sharing_url">Ihre benutzerdefinierte Freigabeadresse …</string>
<string name="settings_custom_sharing_url">Deine persönliche Freigabeadresse …</string>
<string name="set_lock_account">Konto sperren</string>
<string name="set_save_changes">Änderungen speichern</string>
<string name="set_fit_preview">Vorschaubilder anpassen</string>
@ -214,7 +215,7 @@
<string name="settings_time_to">und</string>
<string name="embedded_browser">Benutze internen Browser</string>
<string name="custom_tabs">In-App öffnen mit externem Browser</string>
<string name="expand_cw">Inhaltswarnungen (CWs) automatisch einblenden</string>
<string name="expand_cw">Inhaltswarnungen (CW) automatisch einblenden</string>
<string name="set_led_colour">LED Farbe auswählen:</string>
<string-array name="led_colours">
<item>Blau</item>
@ -230,9 +231,9 @@
<string name="action_mute">Stumm</string>
<string name="action_unmute">Stummschaltung beenden</string>
<string name="request_sent">Anfrage gesendet</string>
<string name="followed_by">folgt dir</string>
<string name="followed_by">folgt Dir</string>
<string name="set_capitalize">Ersten Buchstaben bei Antworten groß schreiben</string>
<string name="set_resize_picture">Bildgröße verändern</string>
<string name="set_resize_picture">Bildgröße ändern</string>
<string name="set_resize_video">Videogröße ändern</string>
<!-- Quick settings for notifications -->
<!-- CACHE -->
@ -258,7 +259,7 @@
<string name="action_lists_add_to">Zur Liste hinzufügen</string>
<string name="action_lists_delete">Liste löschen</string>
<string name="action_lists_title_placeholder">Neuer Listentitel</string>
<string name="action_lists_add_user">Das Konto wurde zur Liste hinzugefügt!</string>
<string name="action_lists_add_user">Das Konto wurde der Liste hinzugefügt!</string>
<string name="action_lists_empty">Du hast noch keine Listen!</string>
<!-- Migration -->
<string name="account_moved_to">%1$s wurde verschoben nach %2$s</string>
@ -270,7 +271,7 @@
<string name="poxy_port">Port</string>
<string name="poxy_login">Login</string>
<string name="poxy_password">Passwort</string>
<string name="set_share_details">Nachricht-Details beim Teilen hinzufügen</string>
<string name="set_share_details">Details des Beitrags beim Teilen hinzufügen</string>
<string name="support_the_app_on_liberapay">Unterstütze die app auf Liberapay</string>
<string name="alert_regex">Es gibt einen Fehler im regulären Ausdruck!</string>
<string name="toast_instance_unavailable">Es wurden keine Timelines in dieser Instanz gefunden!</string>
@ -281,17 +282,17 @@
<string name="endorse">Auf Profil hervorheben</string>
<string name="show_boost">Geteilte Beiträge von %s anzeigen</string>
<string name="unendorse">Nicht mehr im Profil hervorheben</string>
<string name="direct_message">Direktnachrichten</string>
<string name="direct_message">Direktnachricht</string>
<string name="filters">Filter</string>
<string name="action_filters_empty_content">Keine Filter vorhanden. Du kannst durch Klicken auf \"+\" neue Filter erstellen.</string>
<string name="filter_keyword">Schlagwort oder Phrase</string>
<string name="context_home">Startseite</string>
<string name="context_public">Öffentliche Timeline</string>
<string name="context_notification">Benachrichtigungen</string>
<string name="context_conversation">Unterhaltungen</string>
<string name="filter_keyword_explanations">Wird unabhängig vom umgebenen Text oder Inhaltswarnung einer Nachricht verglichen</string>
<string name="context_conversation">Unterhaltung</string>
<string name="filter_keyword_explanations">Wird unabhängig von der Groß-/Kleinschreibung in dem Text oder der Inhaltswarnung eines Beitrags verglichen</string>
<string name="context_drop">Entfernen anstatt zu verstecken</string>
<string name="context_drop_explanations">Gefilterte Nachrichten werden unwiderruflich verschwinden, selbst wenn der Filter später entfernt wird</string>
<string name="context_drop_explanations">Gefilterte Beiträge werden unwiderruflich verschwinden, selbst wenn der Filter später entfernt wird</string>
<string name="context_whole_word_explanations">Wenn das Schlüsselwort oder -phrase nur Buchstaben und Zahlen enthält, wird es nur angewendet werden, wenn es dem ganzen Wort entspricht</string>
<string name="context_whole_word">Ganzes Wort</string>
<string name="filter_context">Kontext filtern</string>
@ -301,36 +302,36 @@
<string name="action_update_filter">Filter aktualisieren</string>
<string name="action_list_add">Du hast noch keine Liste erstellt. Drücke auf den \"+\" Knopf um eine anzulegen.</string>
<string name="expand_image">Versteckte Medien automatisch einblenden</string>
<string name="channel_notif_follow">Neuer Folgender</string>
<string name="channel_notif_boost">Neu geteilt</string>
<string name="channel_notif_follow">Neuer Follower</string>
<string name="channel_notif_boost">Neuer geteilter Beitrag</string>
<string name="channel_notif_fav">Neuer Favorit</string>
<string name="channel_notif_mention">Neue Erwähnung</string>
<string name="channel_notif_poll">Umfrage beendet</string>
<string name="channel_notif_backup">Nachrichten-Sicherung</string>
<string name="channel_notif_backup">Sicherung der Beiträge</string>
<string name="channel_notif_status">Neue Beiträge</string>
<string name="channel_notif_media">Medien Download</string>
<string name="channel_notif_media">Medien-Download</string>
<string name="select_sound">Klingelton auswählen</string>
<string name="set_enable_time_slot">Zeitfenster aktivieren</string>
<string name="block_domain_confirm_message">Möchtest Du %s wirklich sperren\?
<string name="block_domain_confirm_message">Möchtest Du %s wirklich blockieren\?
\n
\nEs werden keine Inhalte aus dieser Domain in einer öffentlichen Timeline oder in Deinen Benachrichtigungen angezeigt. Deine Follower aus dieser Domain werden entfernt.</string>
\nDu wirst keine Inhalte mehr von dieser Domain in einer öffentlichen Timeline oder in Deinen Benachrichtigungen sehen. Deine Follower von dieser Domain werden entfernt.</string>
<string name="block_domain">Blockiere Domäne</string>
<string name="toast_block_domain">Die Domäne ist blockiert</string>
<string name="toast_block_domain">Die Domäne ist geblockt</string>
<string name="retrieve_remote_status">Rufe entfernten Status ab</string>
<string name="peertube_instance">Peertube Instanz</string>
<string name="set_display_emoji">Emoji One verwenden</string>
<string name="information">Information</string>
<string name="set_display_card">Vorschau in allen Nachrichten anzeigen</string>
<string name="set_display_card">Vorschau in allen Beiträgen anzeigen</string>
<string name="account_id_clipbloard">Konto-ID wurde in die Zwischenablage kopiert!</string>
<string name="set_change_locale">Sprache ändern</string>
<string name="truncate_long_toots">Lange Nachrichten kürzen</string>
<string name="set_truncate_toot">Kürze Nachrichten mit mehr als x Zeilen. Null bedeutet deaktiviert.</string>
<string name="truncate_long_toots">Lange Beiträge kürzen</string>
<string name="set_truncate_toot">Kürze Beiträge mit mehr als \'x\' Zeilen. Null bedeutet deaktiviert.</string>
<string name="display_toot_truncate">Mehr anzeigen</string>
<string name="hide_toot_truncate">Weniger anzeigen</string>
<string name="tags_already_stored">Dieser Hashtag existiert bereits!</string>
<string name="schedule_boost">Teilen des Beitrags planen</string>
<string name="boost_scheduled">Teilen geplant!</string>
<string name="no_scheduled_boosts">Nichts geplant!</string>
<string name="schedule_boost">Planen eines geteilten Beitrags</string>
<string name="boost_scheduled">Geteilter Beitrag ist geplant!</string>
<string name="no_scheduled_boosts">Keine geplanten Beiträge zum Anzeigen!</string>
<string name="open_menu">Öffne Menü</string>
<string name="profile_picture">Profilbild</string>
<string name="profile_banner">Profilbanner</string>
@ -340,7 +341,7 @@
<string name="expand_conversation">Unterhaltung erweitern</string>
<string name="custom_emoji_picker">Benutzerdefinierte Emoji Auswahl</string>
<string name="favicon">Favicon</string>
<string name="media_description">Medien zum Hinzufügen einer Beschreibung</string>
<string name="media_description">Eine Beschreibung für Medien hinzufügen (für visuell eingeschränkte Personen)</string>
<string-array name="filter_expire">
<item>Nie</item>
<item>30 Minuten</item>
@ -353,16 +354,16 @@
<!-- languages not translated -->
<string name="languages">Sprachen</string>
<string name="show_media_only">Nur Medien</string>
<string name="show_media_nsfw">Heikle Inhalte anzeigen</string>
<string name="show_media_nsfw">Heikle Inhalte (NSFW) anzeigen</string>
<string name="bot">Bot</string>
<string name="pixelfed_instance">Pixelfed-Instanz</string>
<string name="mastodon_instance">Mastodon-Instanz</string>
<string name="any_tags">Irgendwelche davon</string>
<string name="any_tags">Eines davon</string>
<string name="all_tags">Alle davon</string>
<string name="none_tags">Keine davon</string>
<string name="some_words_any">Eines dieser Wörter (durch Leerzeichen getrennt)</string>
<string name="some_words_all">Alle diese Wörter (durch Leerzeichen getrennt)</string>
<string name="some_tags">Wörter zum Filter hinzufügen (durch Leerzeichen getrennt)</string>
<string name="some_words_any">Einer dieser Hashtags (durch Leerzeichen getrennt)</string>
<string name="some_words_all">Alle dieser Hashtags (durch Leerzeichen getrennt)</string>
<string name="some_tags">Hashtags zum Filter hinzufügen (durch Leerzeichen getrennt)</string>
<string name="change_tag_column">Spaltenname ändern</string>
<string name="misskey_instance">Misskey Instanz</string>
<string name="trending">Angesagt</string>
@ -370,8 +371,8 @@
<string name="category">Kategorie</string>
<string name="description">Beschreibung</string>
<string name="share">Teilen</string>
<string name="toots_server">Nachrichten (Server)</string>
<string name="toots_client">Nachrichten (Gerät)</string>
<string name="toots_server">Beiträge (Server)</string>
<string name="toots_client">Beiträge (Gerät)</string>
<string name="settings_category_label_timelines">Timelines</string>
<string name="settings_category_label_interface">Benutzeroberfläche</string>
<string name="contact">Kontakte</string>
@ -393,7 +394,7 @@
<string name="poll_finish_at">endet um %s</string>
<string name="vote">Abstimmen</string>
<string name="notif_poll">Eine Umfrage, in der Du abgestimmt hast, ist beendet</string>
<string name="notif_poll_self">Eine Ihrer Umfragen wurde beendet</string>
<string name="notif_poll_self">Eine Deiner Umfragen wurde beendet</string>
<string name="settings_category_notif_categories">Kategorien</string>
<string name="move_timeline">Timeline verschieben</string>
<string name="hide_timeline">Timeline ausblenden</string>
@ -406,7 +407,7 @@
<string name="set_sensitive_content">Medien immer als sensibel kennzeichnen</string>
<string name="gnu_instance">GNU-Instanz</string>
<string name="set_forward_tags">Hashtags in Antworten übernehmen</string>
<string name="set_long_press_media">Lange Drücken, um Medien zu speichern</string>
<string name="set_long_press_media">Lange drücken, um Medien zu speichern</string>
<string name="add_tags">Hashtags verwalten</string>
<string name="display_name">Anzeigename</string>
<string name="label_emoji">Emoji</string>
@ -418,10 +419,10 @@
<string name="image_saved">Grafik erfolgreich gespeichert!</string>
<string name="save_image_failed">Grafik konnte nicht gespeichert werden</string>
<string name="add_poll_item">Umfrageelement hinzufügen</string>
<string name="mute_conversation">Thema nicht mehr benachrichtigen</string>
<string name="unmute_conversation">Thema erneut benachrichtigen</string>
<string name="toast_unmute_conversation">Nachrichten zu diesem Thema werden wieder benachrichtigt!</string>
<string name="toast_mute_conversation">Nachrichten zu diesem Thema werden nicht mehr benachrichtigt</string>
<string name="mute_conversation">Unterhaltung stummschalten</string>
<string name="unmute_conversation">Stummschaltung der Unterhaltung aufheben</string>
<string name="toast_unmute_conversation">Die Unterhaltung ist nicht länger stummgeschaltet! Du wirst wieder über Beiträge in dieser Unterhaltung informiert!</string>
<string name="toast_mute_conversation">Die Unterhaltung ist stummgeschaltet! Du wirst nicht mehr über Beiträge in dieser Unterhaltung informiert</string>
<string name="category_general">Allgemein</string>
<string name="category_regional">Regional</string>
<string name="category_art">Kunst</string>
@ -439,7 +440,7 @@
<string name="server_rules">Server-Regeln</string>
<string name="tos">Nutzungsbedingungen</string>
<string name="sign_up">Registrieren</string>
<string name="validation_needed">Diese Instanz verwendet Einladungen. Ihr Konto muss von einem Administrator manuell genehmigt werden, bevor es verwendet werden kann.</string>
<string name="validation_needed">Diese Instanz verwendet Einladungen. Dein Konto muss von einem Administrator manuell genehmigt werden, bevor es verwendet werden kann.</string>
<string name="password_error">Die Passwörter stimmen nicht überein!</string>
<string name="email_error">Ungültige E-Mail-Adresse!</string>
<string name="email_indicator">Du erhältst eine Bestätigungs-E-Mail</string>
@ -454,7 +455,7 @@
\nDu kannst nun Dein Konto verbinden, indem Du <b>%1$s</b> in das erste Feld schreibst und auf die Schaltfläche <b>Verbinden</b> klickst.
\n
\n<b>Wichtig</b>: Wenn Deine Instanz eine Bestätigung benötigt, erhältst Du eine E-Mail, sobald sie geprüft wurde! </string>
<string name="save_draft">Möchtest Du die Nachricht als Entwurf speichern\?</string>
<string name="save_draft">Möchtest Du den Beitrag als Entwurf speichern\?</string>
<string name="administration">Verwaltung</string>
<string name="reports">Berichte</string>
<string name="unresolved">Ungelöst</string>
@ -473,15 +474,15 @@
<string name="unsuspend">Unterbrechen widerrufen</string>
<string name="audio">Die Anwendung benötigt Zugriff auf die Audioaufzeichnung</string>
<string name="voice_message">Sprachnachricht</string>
<string name="set_enable_time_slot_indication">Innerhalb des Zeitfensters sendet die App Benachrichtigungen. Du kannst dieses Zeitfenster mit dem rechten Schieber zurücksetzen (d. h. leise).</string>
<string name="set_enable_time_slot_indication">Innerhalb des Zeitfensters sendet die App Benachrichtigungen. Du kannst dieses Zeitfenster mit dem Auswahlfeld rechts zurücksetzen (z. B. Still).</string>
<string name="set_fit_preview_indication">Vorschauen in den Timelines werden nicht beschnitten</string>
<string name="set_capitalize_indication">Automatisches Einfügen eines Zeilenumbruchs nach einer Erwähnung, um den ersten Buchstaben groß zu schreiben</string>
<string name="settings_title_custom_sharing_indication">Ermöglicht es Inhaltserstellern, den Status ihrer RSS-Feeds zu teilen</string>
<string name="settings_title_custom_sharing_indication">Ermöglicht es Inhaltserstellern, ihre Beiträge als RSS-Feed zu teilen</string>
<string name="compose">Verfassen</string>
<string name="select">Auswählen</string>
<string name="add_instances">Instanz hinzufügen</string>
<string name="set_enable_crash_report">Absturzberichte aktivieren</string>
<string name="set_enable_crash_report_indication">Wenn aktiviert, wird ein Absturzbericht erstellt. Anschließend kannst Du diesen teilen.</string>
<string name="set_enable_crash_report_indication">Wenn aktiviert, wird ein Absturzbericht auf Deinem Mobiltelefon erstellt. Anschließend kannst Du diesen teilen.</string>
<string name="crash_title">Fedilab ist abgestürzt :(</string>
<string name="crash_message">Du kannst mir den Fehlerreport per E-Mail senden. Dies hilft mir bei der Fehlerbehebung:)\n\nDu kannst weitere Inhalte hinzufügen. Danke!</string>
<string name="visibility">Sichtbarkeit</string>
@ -504,10 +505,10 @@
<item>3 Tage</item>
<item>7 Tage</item>
</string-array>
<string name="poll_duplicated_entry">Ihre Umfrage kann keine doppelten Optionen haben!</string>
<string name="set_clear_cache_exit">Cache beim Verlassen löschen</string>
<string name="set_clear_cache_exit_indication">Der Cache (Medien, zwischengespeicherte Nachrichten, Daten aus dem eingebauten Browser) wird automatisch gelöscht, wenn die Anwendung verlassen wird.</string>
<string name="unfollow_confirm">Möchtest Du diesem Konto nicht mehr folgen\?</string>
<string name="poll_duplicated_entry">Deine Umfrage kann keine doppelten Optionen haben!</string>
<string name="set_clear_cache_exit">Zwischenspeicher beim Verlassen löschen</string>
<string name="set_clear_cache_exit_indication">Der Zwischenspeicher (Medien, zwischengespeicherte Beiträge, Daten aus dem eingebauten Browser) wird automatisch gelöscht, wenn die Anwendung verlassen wird.</string>
<string name="unfollow_confirm">Möchtest Du diesem Konto entfolgen\?</string>
<string name="set_unfollow_validation">Bestätigungsdialog vor dem Entfolgen anzeigen</string>
<string name="replace_medium">Medium</string>
<string name="replace_medium_description">Nutze eine alternative Benutzeroberfläche für Medium</string>
@ -519,10 +520,10 @@
<string name="set_resize_video_indication">Erlaube das Komprimieren von Videos während die Qualität erhalten bleibt.</string>
<string name="order_by">Sortieren nach</string>
<string name="link_color_title">Links</string>
<string name="link_color">Ändert die Farbe der Links (URLs, Erwähnungen, Hashtags, etc.) in Nachrichten</string>
<string name="boost_header_color_title">Titelzeile des geteilten Beitrags</string>
<string name="displayname_title">Ändern der Farbe des Anzeigenamens oben in den Nachrichten</string>
<string name="username_title">Ändern der Farbe des Benutzernamens am Anfang von Nachrichten</string>
<string name="link_color">Ändert die Farbe der Links (URLs, Erwähnungen, Hashtags, etc.) in Beiträgen</string>
<string name="boost_header_color_title">Kopfzeile des geteilten Beitrags</string>
<string name="displayname_title">Ändert die Farbe des Anzeigenamens am Beginn eines Beitrags</string>
<string name="username_title">Ändert die Farbe des Benutzernamens am Beginn eines Beitrags</string>
<string name="boost_header_color">Ändert die Farbe der Kopfzeile von geteilten Beiträgen</string>
<string name="background_status_title">Beiträge</string>
<string name="background_status">Hintergrundfarbe der Beiträge in den Timelines</string>
@ -536,7 +537,7 @@
<string name="make_an_action">Aktion ausführen</string>
<string name="translation">Übersetzung</string>
<string name="text_color_title">Textfarbe</string>
<string name="text_color">Ändere die Textfarbe in Nachrichten</string>
<string name="text_color">Ändert die Textfarbe der Beiträge</string>
<string name="pref_custom_theme">Verwende ein benutzerdefiniertes Design</string>
<string name="theming">Farbschema</string>
<string name="data_export_theme">Das Theme wurde exportiert</string>
@ -567,11 +568,11 @@
<string name="verified_by">Verifiziert von %1$s (%2$s)</string>
<string name="action_disabled">Aktion deaktiviert</string>
<string name="action_unfollow">Entfolgen</string>
<string name="error_destination_path">Etwas ist schief gelaufen. Bitte überprüfen Sie Ihr Downloadverzeichnis in den Einstellungen.</string>
<string name="error_destination_path">Etwas ist schief gelaufen. Bitte überprüfe Dein Download-Verzeichnis in den Einstellungen.</string>
<string name="action_announcements">Ankündigungen</string>
<string name="no_announcements">Keine Ankündigungen!</string>
<string name="add_reaction">Eine Reaktion hinzufügen</string>
<string name="set_video_cache">Video-Cache in MB, Null bedeutet keinen Cache.</string>
<string name="set_video_cache">Video-Zwischenspeicher in MB, Null bedeutet keinen Zwischenspeicher.</string>
<string name="set_watermark">Wasserzeichen</string>
<string name="set_watermark_indication">Automatisches Hinzufügen eines Wasserzeichens am unteren Rand von Bildern. Der Text kann für jedes Konto angepasst werden.</string>
<string name="no_distributors_found">Keine Dienste gefunden!</string>
@ -584,7 +585,7 @@
<string name="instance_not_valid">Diese Instanz scheint nicht gültig zu sein!</string>
<string name="boosted_by">Geteilt von</string>
<string name="favourited_by">Favoritisiert von</string>
<string name="followers_only">Nur für Follower</string>
<string name="followers_only">Nur Follower</string>
<string name="eg_sensitive_content">Z. B.: Sensibler Inhalt</string>
<string name="add_status">Status hinzufügen</string>
<string name="remove_status">Status entfernen</string>
@ -593,8 +594,8 @@
<string name="stop_recording">Aufnahme anhalten</string>
<string name="report_val1">Ich mag es nicht</string>
<string name="report_val2">Es ist Spam</string>
<string name="toast_bookmark">Die Nachricht wurde zu Deinen Lesezeichen hinzugefügt!</string>
<string name="toast_unbookmark">Die Nachricht wurde von Deinen Lesezeichen entfernt!</string>
<string name="toast_bookmark">Der Beitrag wurde Deinen Lesezeichen hinzugefügt!</string>
<string name="toast_unbookmark">Der Beitrag wurde aus Deinen Lesezeichen entfernt!</string>
<string name="set_accounts_page">Anzahl der Konten pro Ladevorgang</string>
<string name="category_music">Musik</string>
<string name="cannot_be_empty">Dieses Feld kann nicht leer sein!</string>
@ -626,18 +627,18 @@
<string name="report_more_forward">Weiterleiten an %1$s</string>
<string name="join_the_fediverse">Komm ins Fediverse</string>
<string name="notif_display_mentions">Erwähnungen</string>
<string name="about_mastodon">„Mastodon ist nicht wie Twitter oder Facebook, es besteht aus einem Netzwerk von tausenden, durch unterschiedliche Organisationen und Einzelpersonen betriebene, Gemeinschaften, die ein nahtloses Soziale-Medien-Erlebnis bieten.“</string>
<string name="about_mastodon">„Mastodon ist keine einzelne Webseite wie Twitter oder Facebook, es besteht aus einem Netzwerk von tausenden, durch unterschiedliche Organisationen und Einzelpersonen betriebene Gemeinschaften, die ein nahtloses Soziale-Medien-Erlebnis bieten.“</string>
<string name="notif_display_favourites">Favoriten</string>
<string name="save_changes">Änderungen speichern</string>
<string name="locked">Gesperrt</string>
<string name="unlocked">Entsperrt</string>
<string name="add_field">Feld hinzufügen</string>
<string name="add_filter">Filter hinzufügen</string>
<string name="delete_notification_all_warning">Bist Du Dir sicher, dass Du alle Benachrichtigungen löschen willst\? Das kann nicht rückgängig gemacht werden.</string>
<string name="delete_notification_all_warning">Bist Du sicher, dass Du alle Benachrichtigungen löschen willst\? Das kann nicht rückgängig gemacht werden.</string>
<string name="display_all_categories">Zeige alle Kategorien</string>
<string name="notif_display_poll_results">Ergebnisse der Umfrage</string>
<string name="mark_all_as_read">Alle Benachrichtigungen als gelesen markieren</string>
<string name="clear_all_notif">Alle Benachrichtigungen löschen</string>
<string name="clear_all_notif">Alle Benachrichtigungen entfernen</string>
<string name="scheduled">Geplant</string>
<string name="profiled_updated">Profil wurde aktualisiert!</string>
<string name="not_valid_list_name">Listenname ist nicht gültig!</string>
@ -647,7 +648,7 @@
<string name="type_of_notifications">Art der Benachrichtigungen</string>
<string name="disable_notifications">Benachrichtigungen deaktivieren</string>
<string name="notification_sounds">Benachrichtigungstöne</string>
<string name="type_of_notifications_title">Wähle die Art der Benachrichtigung</string>
<string name="type_of_notifications_title">Wähle die Art der Benachrichtigungen</string>
<string name="pref_contributor">Themen der Mitwirkenden</string>
<string name="customize_timelines">Timelines anpassen</string>
<string name="select_a_theme">Ein Thema wählen</string>
@ -670,7 +671,7 @@
<string name="report_val_more2">Bösartige Links, gefälschtes Engagement oder sich wiederholende Antworten</string>
<string name="report_indication_title_status">Sage uns, was es mit diesem Beitrag auf sich hat</string>
<string name="report_1_mute_title">Stummschalten %1$s</string>
<string name="report_1_block_title">Blockieren %1$s</string>
<string name="report_1_block_title">Blockiere %1$s</string>
<string name="invite_join_the_fediverse">Hallo! Wir laden Dich ein, dem Fediverse beizutreten.</string>
<string name="set_bot_content">Bot-Konto</string>
<string name="interactions">Interaktionen</string>
@ -678,7 +679,7 @@
<string name="pref_custom_theme_new_summary">Erlaubt die Erstellung des eigenen Themas</string>
<string name="pref_theme_base_summary">Wähle, ob die Basis des Themas dunkel oder hell sein soll</string>
<string name="types_of_notifications_to_display">Arten der anzuzeigenden Benachrichtigungen</string>
<string name="toots_visibility_title">Standardmäßige Sichtbarkeit der Nachrichten:</string>
<string name="toots_visibility_title">Standardmäßige Sichtbarkeit der Beiträge:</string>
<string name="set_notifications_page">Anzahl an Benachrichtigungen pro Ladevorgang</string>
<string name="replace_instagram_description">Nutze eine alternative Benutzeroberfläche für Instagram</string>
<string name="replace_instagram_host">Instagram Frontend Domain</string>
@ -687,15 +688,15 @@
<string name="pref_theme_base">Grundlage des Themas</string>
<string name="notifications_are">Während dieses Zeitfensters</string>
<string name="set_display_bookmark_indication">Schaltfläche \"Lesezeichen\" immer anzeigen</string>
<string name="report_more_remote">Das Konto stammt von einem anderen Server. Die anonymisierte Kopie des Berichts auch dorthin senden\?</string>
<string name="report_more_remote">Das Konto stammt von einem anderen Server. Eine anonymisierte Kopie des Berichts auch dorthin senden\?</string>
<string name="report_indication_title_status_more">Wähle die beste Übereinstimmung</string>
<string name="report_1_unfollow_title">Entfolge %1$s</string>
<string name="report_1_unfollow">Du folgst diesem Konto. Um die Beiträge nicht mehr auf Deiner Startseite zu sehen, entfolge ihm.</string>
<string name="report_1_mute">Du wirst ihre Beiträge nicht mehr sehen. Sie können Dir immer noch folgen und Deine Beiträge sehen und wissen nicht, dass sie stummgeschaltet sind.</string>
<string name="report_1_block">Du wirst ihre Beiträge nicht mehr sehen. Sie werden Deine Beiträge nicht sehen und Dir nicht mehr folgen können. Sie werden erkennen können, dass sie blockiert sind.</string>
<string name="report_1_block">Du wirst ihre Beiträge nicht mehr sehen. Sie werden Deine Beiträge nicht sehen und Dir nicht mehr folgen können. Sie können erkennen, dass sie geblockt sind.</string>
<string name="notif_display_reblogs">Geteilte Beiträge</string>
<string name="notif_display_updates_from_people">Aktualisierungen Anderer</string>
<string name="notif_display_follows">Folgende</string>
<string name="notif_display_follows">Folgt</string>
<string name="also_boosted_by">Auch geteilt von:</string>
<string name="set_unfollow_validation_title">Entfolgen bestätigen</string>
<string name="also_favourite_by">"Auch favorisiert von: "</string>
@ -712,38 +713,38 @@
<string name="tap_here_to_refresh_poll">Hier tippen, um die Umfrage zu aktualisieren</string>
<string name="label_line">Linie</string>
<string name="label_rectangle">Eckig</string>
<string name="files_cache_size">Datei-Cache Größe</string>
<string name="fetch_more_messages">Mehr Nachrichten laden…</string>
<string name="messages_stored_in_drafts">Nachrichten in den Entwürfen gespeichert</string>
<string name="files_cache_size">Größe des Datei-Zwischenspeichers</string>
<string name="fetch_more_messages">Mehr Beiträge laden…</string>
<string name="messages_stored_in_drafts">Beiträge als Entwürfe gespeichert</string>
<string name="label_oval">Rund</string>
<string name="label_eraser_mode">Radier-Modus</string>
<string name="action_announcement_from_to">Ankündigung · %1$s - %2$s</string>
<string name="delete_cache">Cache leeren</string>
<string name="messages_in_cache_for_home">Nachrichten im Cache für Startseite</string>
<string name="messages_in_cache_for_other_timelines">Nachrichten im Cache für andere Timelines</string>
<string name="clear_cache">Cache leeren</string>
<string name="delete_cache_message">Bist Du Dir sicher den Cache zu leeren\? Angehängte Bilder/Videos werden in gespeicherten Entwürfen geschlöscht.</string>
<string name="delete_cache">Zwischenspeicher löschen</string>
<string name="messages_in_cache_for_home">Zwischenspeicher für Beiträge auf der Startseite</string>
<string name="messages_in_cache_for_other_timelines">Zwischenspeicher für Beiträge in den anderen Timelines</string>
<string name="clear_cache">Zwischenspeicher leeren</string>
<string name="delete_cache_message">Bist Du Dir sicher, den Zwischenspeicher zu löschen\? Wenn Du gespeicherte Entwürfe mit angehängten Medien hast, gehen die Anhänge verloren.</string>
<string name="msg_save_image">Verlassen, ohne das Bild zu speichern\?</string>
<string name="label_shape">Form</string>
<string name="domain">Domäne</string>
<string name="staff">Personal</string>
<string name="message_language">Nachrichtensprache</string>
<string name="message_language">Sprache der Beiträge</string>
<string name="my_instance">Meine Instanz</string>
<string name="icon_size">Symbolgröße</string>
<string name="export_settings">Einstellungen exportieren</string>
<string name="load_media_type_title">Vorschaubilder für Medien laden</string>
<string name="display_timelines">Timelines anzeigen</string>
<string name="set_display_counters_description">Die Anzahl neuer Nachrichten in den Timelines wird in der Registerkarte angezeigt</string>
<string name="set_display_counters_description">Die Anzahl neuer Beiträge wird in der Registerkarte der Timeline angezeigt</string>
<string name="remember_position">Position in den Timelines merken</string>
<string name="set_use_cache_indication">Timelines werden zwischengespeichert, um die Anwendung zu beschleunigen.</string>
<string name="cached_messages">Zwischengespeicherte Nachricht</string>
<string name="cached_messages">Zwischengespeicherter Beitrag</string>
<string name="reply">Antwort</string>
<string name="display_options">Anzeigeeinstellungen</string>
<string name="set_display_counters">Counter anzeigen</string>
<string name="set_display_counters">Anzahl anzeigen</string>
<string name="my_app">Meine App</string>
<string name="my_account">Mein Konto</string>
<string name="release_notes">Versionshinweise</string>
<string name="display_media_notification">Medien in Benachrichtigung anzeigen</string>
<string name="display_media_notification">Medien in Benachrichtigungen anzeigen</string>
<string name="pickup_logo">Logo auswählen</string>
<string name="change_logo">Logo ändern</string>
<string name="change_logo_description">Ändert das App-Logo auf dem Gerät</string>
@ -754,20 +755,20 @@
<string name="open_draft">Entwurf öffnen</string>
<string name="import_settings">Einstellungen importieren</string>
<string name="permission_missing">Berechtigung nicht gestattet!</string>
<string name="action_pin">Nachricht anheften</string>
<string name="action_unpin">Nachricht nicht mehr anheften</string>
<string name="action_pin">Beitrag anheften</string>
<string name="action_unpin">Beitrag nicht mehr anheften</string>
<string name="load_settings">Exportierte Einstellungen laden</string>
<string name="toast_unpin">Die Nachricht ist nicht mehr angeheftet!</string>
<string name="set_display_counter">Zeige Zähler für Nachrichten</string>
<string name="toast_pin">Die Nachricht wurde angeheftet</string>
<string name="set_live_translate_title">Nachrichten übersetzen</string>
<string name="set_use_cache">Cache verwenden</string>
<string name="toast_unpin">Der Beitrag ist nicht mehr angeheftet!</string>
<string name="set_display_counter">Zeige Zähler für Beiträge</string>
<string name="toast_pin">Der Beitrag wurde angeheftet</string>
<string name="set_live_translate_title">Beitrag übersetzen</string>
<string name="set_use_cache">Zwischenspeicher verwenden</string>
<string name="display_media">Medien anzeigen</string>
<string name="set_timelines_in_a_list_title">Timelines als Liste</string>
<string name="set_timelines_in_a_list">Wenn aktiv, werden alle angepinnten Timelines in einem Dropdown-Menü angezeigt</string>
<string name="set_single_topbar_title">Einfache Actionbar</string>
<string name="view_the_original_message">Originalnachricht öffnen</string>
<string name="type_of_notifications_delay_title">Benachrichtigungsabrufzeit</string>
<string name="view_the_original_message">Original-Beitrag öffnen</string>
<string name="type_of_notifications_delay_title">Abruf-Intervall</string>
<string name="fetch_notifications">Hole Benachrichtigungen</string>
<string name="set_single_topbar">Wenn aktiv, hat die App nur eine Anzeigeleiste für alle Timelines</string>
<string name="media_cannot_be_uploaded">Datei konnte nicht hochgeladen werden!</string>
@ -776,7 +777,7 @@
<string name="set_push_notifications_delay">Verzögerung zwischen jedem Aktualisieren setzen</string>
<string name="push_distributors">Push-Dienst</string>
<string name="toast_token">Die App konnte kein Token abrufen</string>
<string name="edit_message">Nachricht bearbeiten</string>
<string name="edit_message">Beitrag bearbeiten</string>
<string name="action_pinned_delete">Angepinnte Timelines löschen\?</string>
<string name="notif_submitted_report">Bericht eingereicht</string>
<string name="action_privacy_policy">Datenschutz-Bestimmungen</string>
@ -784,9 +785,9 @@
<string name="unpin_timeline_description">Bist Du sicher, dass diese Timeline nicht mehr angepinnt sein soll\?</string>
<string name="blocked_domains">Geblockte Domänen</string>
<string name="full_date_edited">%1$s bearbeitet %2$s</string>
<string name="unblock_domain">Domäne freigeben</string>
<string name="unblock_domain">Domäne entsperren</string>
<string name="no_blocked_domains">Du hast keine Domänen geblockt</string>
<string name="unblock_domain_confirm">Sicher, dass Du %1$s wieder freigeben willst\?</string>
<string name="unblock_domain_confirm">Sicher, dass Du das Blockieren von %1$s aufheben möchtest\?</string>
<string name="Suggestions">Vorschläge</string>
<string name="not_interested">Nicht interessiert</string>
<string name="unassign">Zuweisung aufheben</string>
@ -798,9 +799,9 @@
<string name="account_rejected">Konto abgelehnt</string>
<string name="account_approved">Konto genehmigt</string>
<string name="report">Meldung</string>
<string name="display_media_notification_summary">Medien in Benachrichtigungen für geteilte Beiträge und Favoriten werden angezeigt</string>
<string name="display_media_notification_summary">Medien werden in Benachrichtigungen für geteilte Beiträge und Favoriten angezeigt</string>
<string name="set_live_translate">Übersetzung in eine bestimmte Sprache erzwingen. Wähle den ersten Wert, um auf Geräteeinstellungen zurückzusetzen</string>
<string name="status_history">Nachrichten-Verlauf</string>
<string name="status_history">Beitragsverlauf</string>
<string name="edited_message_at">Bearbeitet am %1$s</string>
<string name="created_message_at">Erstellt am %1$s</string>
<string name="set_unlisted_replies">Nicht gelistete Antworten</string>
@ -810,7 +811,7 @@
<string name="recent_ip">Aktuelle IP</string>
<string name="allow">Erlauben</string>
<string name="warn">Warnen</string>
<string name="email_user">Benutzer per eMail benachrichtigen</string>
<string name="email_user">Benutzer per E-Mail benachrichtigen</string>
<string name="custom_warning">Benutzerdefinierte Warnung</string>
<string name="list_reported_statuses">Status von Meldungen</string>
<string name="silenced">Stummgeschaltet</string>
@ -826,20 +827,20 @@
<string name="restart_the_app">App neu starten\?</string>
<string name="restart">Neustart</string>
<string name="restart_the_app_theme">Du musst die App neu starten um die Änderungen anzuwenden.</string>
<string name="action_followed_tag_empty">Du folgst bisher keinen Hashtags!</string>
<string name="action_unfollow_tag">Hashtag nicht mehr folgen</string>
<string name="action_unfollow_tag_confirm">Bist Du sicher dass Du diesem Hashtag nicht mehr folgen willst\?</string>
<string name="unfollow">Nicht mehr folgen</string>
<string name="action_followed_tag_empty">Du folgst keinen Hashtags!</string>
<string name="action_unfollow_tag">Hashtag entfolgen</string>
<string name="action_unfollow_tag_confirm">Bist Du sicher, dass Du diesem Hashtag entfolgen möchtest\?</string>
<string name="unfollow">Entfolgen</string>
<string name="action_tag_follow">Hashtag folgen</string>
<string name="write_the_tag_to_follow">Schreibe den Hashtag, dem Du folgen möchtest</string>
<string name="followed_tags">Gefolgten Hashtags</string>
<string name="followed_tags">Gefolgte Hashtags</string>
<string name="follow_tag">Hashtag folgen</string>
<string name="aggregate_notifications_summary">Falls aktiv wird die App alle zusammenhängenden Benachrichtigungen einklappen</string>
<string name="aggregate_notifications_summary">Wenn aktiviert, wird die App alle zusammengehörenden Benachrichtigungen einklappen</string>
<string name="action_lists_edit">Liste bearbeiten</string>
<string name="profiles">Profile</string>
<string name="toast_feature_not_supported">Deine Instanz scheint diese Funktion nicht zu unterstützen!</string>
<string name="watch_trends_for_instance">Erkunde Trends dieser Instanz</string>
<string name="notif_update">Nachricht bearbeiten</string>
<string name="notif_update">Beitrag wurde bearbeitet</string>
<string name="notif_display_updates">Aktualisierungen</string>
<string name="hide_with_warning">Mit Warnung verstecken</string>
<string name="hide_completely">Vollständig verstecken</string>
@ -854,13 +855,13 @@
<string name="delete_timeline">Timeline löschen</string>
<string name="notif_signed_up">Angemeldet</string>
<string name="channel_notif_signup">Neue Registrierung</string>
<string name="notif_sign_up">Eine Benutzer hat sich registriert</string>
<string name="notif_sign_up">Ein Benutzer hat sich registriert</string>
<string name="display_remote_profile">Remote-Profil anzeigen</string>
<string name="toast_fetch_error">Die App kann keine Remote-Daten finden!</string>
<string name="set_notif_update">Über Updates benachrichtigen</string>
<string name="set_notif_update">Benachrichtigung über Updates</string>
<string name="domains">Domänen</string>
<string name="channel_notif_update">Neues Update</string>
<string name="notif_update_push">Eine Nachricht die Du geteilt hast wurde bearbeitet</string>
<string name="notif_update_push">Ein Beitrag, den Du geteilt hast, wurde bearbeitet</string>
<string name="notif_report">Ein Benutzer hat eine Meldung gesendet</string>
<string name="sign_ups">Registrierungen</string>
<string name="channel_notif_report">Neue Meldung</string>
@ -875,11 +876,11 @@
<string name="set_unlisted_replies_indication">Betrifft nur \"öffentliche\" Antworten. Falls aktiv, werden Deine Antworten automatisch \"nicht gelistet\" statt \"öffentlich\"</string>
<string name="login_status">Anmeldestatus</string>
<string name="set_language_picker_title">Sprachen in der Auswahl</string>
<string name="set_language_picker">Erlaube die Liste der Sprachen in der Auswahl beim Verfassen einer Nachricht zu reduzieren.</string>
<string name="set_language_picker">Reduziert die Liste der Sprachen-Auswahl beim Verfassen eines Beitrags.</string>
<string name="not_valid_tag_name">Bezeichnung des Hashtags ist nicht zulässig!</string>
<string name="severity">Schweregrad</string>
<string name="reject_media">Medien ablehnen</string>
<string name="admin_reject_media">Mediendateien ablehnen</string>
<string name="admin_reject_media">Medien-Dateien ablehnen</string>
<string name="admin_domainblock_reject_media">Ignoriere alle Meldungen die von dieser Domäne kommen. Für Suspendierungen irrelevant</string>
<string name="admin_reject_reports">Berichte ablehnen</string>
<string name="admin_reject_obfuscate">Domänenname verschleiern</string>
@ -890,7 +891,7 @@
<string name="create_domain_block">Domänen-Blockierung erstellen</string>
<string name="open_with_account">Mit einem anderen Konto öffnen</string>
<string name="reject_reports">Berichte ablehnen</string>
<string name="admin_domainblock_domain">Die Sperrung der Domäne verhindert nicht die Erstellung von Konto-Einträgen in der Datenbank, sondern wendet rückwirkend und automatisch bestimmte Moderations-Methoden auf diese Konten an.</string>
<string name="admin_domainblock_domain">Das Blockieren der Domäne verhindert nicht die Erstellung von Konto-Einträgen in der Datenbank, sondern wendet rückwirkend und automatisch bestimmte Moderations-Methoden auf diese Konten an.</string>
<string name="admin_domainblock_reject_reports">Ignoriere alle Meldungen die von dieser Domäne kommen. Für Suspendierungen irrelevant</string>
<string name="admin_domainblock_severity">Stummschaltung macht die Beiträge des Kontos für alle unsichtbar, die ihm nicht folgen. Suspendierung entfernt alle Inhalte, Medien und Profildaten des Kontos. Verwende Keine, wenn Du nur die Mediendateien ablehnen möchtest.</string>
<string name="admin_domainblock_reject_obfuscate">Verschleiere teilweise den Domänennamen in der Liste, wenn die Verteilung der Liste der Domänen-Beschränkunden aktiviert ist</string>
@ -916,15 +917,15 @@
<string name="set_custom_colors">Eigene Farben auswählen</string>
<string name="light_custom_colors">Hell - Eigene Farben</string>
<string name="cark_custom_colors">Dunkel - Eigene Farben</string>
<string name="display_remote_conversation">Entfernte Konversation anzeigen</string>
<string name="toast_on_your_instance">Die Konversation begann auf Deiner Instanz!</string>
<string name="toast_error_fetch_message">Die Anwendung hat die entfernte Nachricht nicht gefunden.</string>
<string name="display_remote_conversation">Entfernte Unterhaltung (auf einer anderen Instanz) anzeigen</string>
<string name="toast_on_your_instance">Die Unterhaltung begann auf Deiner Instanz!</string>
<string name="toast_error_fetch_message">Die Anwendung konnte den Beitrag (einer anderen Instanz) nicht finden.</string>
<string name="mute_tag_action">Hashtag stummschalten</string>
<string name="unpin_tag">Anpinnen des Hashtags aufheben</string>
<string name="unmute_tag_action">Stummschaltung des Hashtags aufheben</string>
<string name="toast_try_later">Bitte später nochmal versuchen.</string>
<string name="pin_tag">Hashtag anpinnen</string>
<string name="put_all_accounts_in_home_muted">Alle Konten auf der Startseite stumm schalten.</string>
<string name="put_all_accounts_in_home_muted">Alle Konten auf der Startseite stummschalten.</string>
<string name="add_all_users_home_muted">Alle Benutzer auf der Startseite stummschalten</string>
<string name="import_data">Daten importieren</string>
<string name="group_reblogs">Gruppiere geteilte Beiträge auf der Startseite</string>
@ -934,8 +935,8 @@
<string name="unfollow_tag">Hashtag entfolgen</string>
<string name="mute_home">Auf der Startseite stummschalten</string>
<string name="unmute_home">Stummschaltung auf der Startseite aufheben</string>
<string name="set_remove_left_margin">Entfernt den linken Rand in den Timelines, um Nachrichten kompakter darzustellen</string>
<string name="set_display_translate_indication">Übersetzungs-Knopf immer anzeigen</string>
<string name="set_remove_left_margin">Entfernt den linken Rand in den Timelines, um Beiträge kompakter darzustellen</string>
<string name="set_display_translate_indication">Schaltfläche \"Übersetzen\" immer anzeigen</string>
<string name="set_cardview">Kartenansicht</string>
<string name="set_remove_left_margin_title">Entferne linken Rand</string>
<string name="version">Version</string>
@ -943,4 +944,24 @@
<string name="set_translator">Übersetzungsdienst</string>
<string name="set_translator_version">Übersetzungsdienst-Version</string>
<string name="translator">Übersetzungsdienst</string>
<string name="icons_visibility">Sichtbarkeit der Symbole</string>
<string name="icons_visibility_summary">Du kannst die Symbole am unteren Rand bedenkenlos entfernen, um mehr Platz zu erhalten. Sie befinden sich auch im Untermenü.</string>
<string name="post_format">Format des Beitrags</string>
<string name="set_post_format">Format des Beitrags</string>
<string name="icons_extra_features">Symbole für zusätzliche Funktionen</string>
<string name="set_display_quote_indication">Zeige den Knopf zum \"Zitieren\"</string>
<string name="set_display_reaction_indication">Zeige die Knöpfe für \"Reaktionen\"</string>
<string name="set_extand_extra_features_title">Zusätzliche Funktionen</string>
<string name="set_extand_extra_features">Wenn diese Option aktiviert ist, zeigt die App zusätzliche Funktionen an. Diese Funktion ist für soziale Plattformen wie Pleroma, Akkoma oder Glitch Social</string>
<string name="icons_extra_features_visibility_summary">Sofern Deine Instanz nicht alle zusätzlichen Funktionen unterstützt, kannst Du diese Symbole ausblenden</string>
<string name="v_list">Liste</string>
<string name="self">Eigene</string>
<string name="following">Gefolgte</string>
<string name="reply_visibility">Sichtbarkeit von Antworten</string>
<string name="exclude_visibility">Sichtbarkeit ausschließen</string>
<string name="bubble">Blase</string>
<string name="set_remote_profile">Die Anwendung ruft öffentlich verfügbare Profile ab, um deren Beiträge darzustellen. Interaktionen mit föderierten Beiträgen benötigen einen zusätzlichen Arbeitsschritt.</string>
<string name="set_remote_profile_title">Profile auf anderen Instanzen</string>
<string name="local_only">Nur Lokal</string>
<string name="set_display_local_only">Zeige den Knopf \"Nur Lokal\"</string>
</resources>

@ -933,4 +933,29 @@
<string name="manage_accounts">Xestionar contas</string>
<string name="set_remove_left_margin_title">Eliminar marxe esquerda</string>
<string name="set_remove_left_margin">Eliminar a marxe esquerda nas cronoloxías para compactar máis as mensaxes</string>
<string name="set_translator">Tradutor</string>
<string name="set_display_local_only">Mostrar botón \"Só local\"</string>
<string name="api_key">Chave da API do tradutor</string>
<string name="version">Versión</string>
<string name="set_extand_extra_features">Ao activar esta opción a app mostrará características extra. Temos esta función para software social tipo Pleroma, Akkoma ou Glitch Social</string>
<string name="set_post_format">Formato da publicación</string>
<string name="icons_extra_features">Iconas para Extras</string>
<string name="icons_extra_features_visibility_summary">Se a túa instancia non acepta características extra, podes agochar estas iconas</string>
<string name="set_display_quote_indication">Mostrar botón \"Cita\"</string>
<string name="set_display_reaction_indication">Mostrar botóns de \"Reaccións\"</string>
<string name="self">Propio</string>
<string name="following">Seguindo</string>
<string name="set_remote_profile_title">Perfís remotos</string>
<string name="local_only">Só local</string>
<string name="bubble">Burbulla</string>
<string name="v_list">Lista</string>
<string name="reply_visibility">Visibilidade da resposta</string>
<string name="exclude_visibility">Visibilidade da exclusión</string>
<string name="translator">Tradutor</string>
<string name="set_extand_extra_features_title">Características extra</string>
<string name="icons_visibility">Visibilidade das iconas</string>
<string name="post_format">Formato da publicación</string>
<string name="set_translator_version">Versión do tradutor</string>
<string name="icons_visibility_summary">Podes agochar tranquilamente estas iconas ao pé para ter máis espazo. Están tamén no submenú.</string>
<string name="set_remote_profile">A app mostrará públicamente os perfís para obter tódalas mensaxes. As interaccións precisarán un paso extra para federar as mensaxes.</string>
</resources>

@ -185,7 +185,7 @@
<string name="nothing_to_do">何もすることはできません</string>
<string name="toast_error_translate">翻訳中にエラーが発生しました!</string>
<!-- Settings -->
<string name="set_toots_page">一回あたりに読み込む投稿</string>
<string name="set_toots_page">1回あたりに読み込む投稿の</string>
<string name="set_disable_gif">GIFアバターを無効にする</string>
<string name="set_notif_follow">フォローされたときに通知する</string>
<string name="set_notif_follow_share">投稿がブーストされたときに通知する</string>
@ -501,7 +501,7 @@
<string name="unfollow_confirm">このアカウントのフォローを解除しますか?</string>
<string name="set_unfollow_validation">フォローを解除する前に確認を表示する</string>
<string name="replace_medium">Medium</string>
<string name="replace_medium_description">Mediumの代替フロントエンドを使用す</string>
<string name="replace_medium_description">Mediumの代替フロントエンドを使用しま</string>
<string name="replace_medium_host">Mediumフロントエンドのドメイン</string>
<string name="set_push_notifications">プッシュ通知システムを使用してリアルタイムに通知を取得します。</string>
<string name="action_add_notes">メモを追加</string>
@ -613,11 +613,11 @@
<string name="set_notifications_page">1回あたりに読み込む通知の数</string>
<string name="category_music">音楽</string>
<string name="replace_twitter">Twitter</string>
<string name="replace_twitter_description">Twitterの代替フロントエンドを使用す</string>
<string name="replace_twitter_description">Twitterの代替フロントエンドを使用しま</string>
<string name="replace_twitter_host">Twitterフロントエンドのドメイン</string>
<string name="replace_instagram_host">Instagramフロントエンドのドメイン</string>
<string name="replace_reddit">Reddit</string>
<string name="replace_reddit_description">Redditの代替フロントエンドを使用す</string>
<string name="replace_reddit_description">Redditの代替フロントエンドを使用しま</string>
<string name="keepon">続行</string>
<string name="category_custom">カスタム</string>
<string name="boosted_by">次のユーザーにブーストされました</string>
@ -660,11 +660,11 @@
<string name="set_accounts_page">1回あたりに読み込むアカウントの数</string>
<string name="cannot_be_empty">この項目は空欄にできません!</string>
<string name="replace_youtube">YouTube</string>
<string name="replace_youtube_description">YouTubeの代替フロントエンドを使用す</string>
<string name="replace_youtube_description">YouTubeの代替フロントエンドを使用しま</string>
<string name="replace_youtube_host">YouTubeフロントエンドのドメイン</string>
<string name="replace_reddit_host">Redditフロントエンドのドメイン</string>
<string name="replace_instagram">Instagram</string>
<string name="replace_instagram_description">Instagramの代替フロントエンドを使用す</string>
<string name="replace_instagram_description">Instagramの代替フロントエンドを使用しま</string>
<string name="followers_only">フォロワーのみ</string>
<string name="add_status">ステータスを追加</string>
<string name="instance_not_valid">このサーバーは無効なようです!</string>
@ -861,7 +861,7 @@
<string name="notif_signed_up">登録しました</string>
<string name="import_data">データをインポート</string>
<string name="admin_reject_reports">通報を却下</string>
<string name="set_notif_update">新着情報の通知</string>
<string name="set_notif_update">新着情報を通知する</string>
<string name="set_notif_admin_report">新しい通報(モデレーター)</string>
<string name="channel_notif_update">新着情報</string>
<string name="channel_notif_signup">新規登録</string>
@ -890,7 +890,7 @@
<string name="keep_notifications">通知を消去しない</string>
<string name="set_dynamic_color">ダイナミックカラー</string>
<string name="set_dynamic_color_indication">個人設定の壁紙の配色に合わせます。</string>
<string name="set_display_translate_indication">翻訳ボタンを常に表示しま</string>
<string name="set_display_translate_indication">翻訳ボタンを常に表示す</string>
<string name="reject_reports">通報を却下</string>
<string name="admin_reject_obfuscate">ドメイン名の難読化</string>
<string name="public_comment">公開コメント</string>
@ -904,7 +904,7 @@
<string name="set_custom_colors">カスタムカラーの設定</string>
<string name="toast_try_later">時間をおいて再試行してください。</string>
<string name="mute_home">ホームでのミュート</string>
<string name="set_remove_left_margin">タイムライン上の左のマージンを削除し、投稿をよりコンパクトにします</string>
<string name="set_remove_left_margin">タイムライン上の左の余白をなくし、投稿をよりコンパクトにします</string>
<string name="notif_reported">通報しました</string>
<string name="domains">ドメイン</string>
<string name="set_notif_user_sign_up">新規登録(モデレーター)</string>
@ -921,12 +921,37 @@
<string name="put_all_accounts_in_home_muted">全てのアカウントがホームタイムライン上でミュートされます。</string>
<string name="add_all_users_home_muted">全てのユーザーをホームでのミュート対象として追加</string>
<string name="mute_them_all">選択をミュート</string>
<string name="group_reblogs">ホームタイムラインでブーストをグループ化</string>
<string name="group_reblogs">ホームタイムラインでブーストをグループ化する</string>
<string name="manage_accounts">アカウントの管理</string>
<string name="set_remove_left_margin_title">左のマージンを削除</string>
<string name="set_remove_left_margin_title">左の余白を詰める</string>
<string name="admin_domainblock_reject_reports">このドメインからの通報を全て無視します。サスペンドとは無関係です</string>
<string name="admin_domainblock_reject_obfuscate">ドメイン制限リストの公開が有効なとき、リスト内のドメイン名を一部難読化します</string>
<string name="private_comment">内部コメント</string>
<string name="admin_domainblock_private_comment">このドメイン制限に関するコメントで、モデレーターによって内部で使用されます。</string>
<string name="admin_domainblock_public_comment">このドメイン制限に関するコメントで、ドメイン制限リストの公開が有効な場合に一般外部向けに使用されます。</string>
<string name="set_translator_version">翻訳ツールのバージョン</string>
<string name="set_display_quote_indication">「引用」ボタンを表示</string>
<string name="bubble">バブル</string>
<string name="exclude_visibility">表示設定を除外する</string>
<string name="following">フォロー中</string>
<string name="self">自分</string>
<string name="set_remote_profile_title">リモートプロフィール</string>
<string name="set_translator">翻訳</string>
<string name="api_key">翻訳ツールのAPIキー</string>
<string name="version">バージョン</string>
<string name="post_format">投稿形式</string>
<string name="icons_visibility_summary">スペースを増やすために下部にあるこれらのアイコンを安全に非表示にできます。サブメニューからも利用可能です。</string>
<string name="set_extand_extra_features_title">追加機能</string>
<string name="set_extand_extra_features">このオプションを有効にするとアプリは追加の機能を表示します。これはPleromaやAkkoma、Glitch Socialなどのソフトウェア向きの機能です</string>
<string name="icons_visibility">アイコンの表示設定</string>
<string name="set_post_format">投稿形式</string>
<string name="icons_extra_features">追加機能のアイコン</string>
<string name="icons_extra_features_visibility_summary">サーバーが一部の追加機能を無効にしている場合、それらのアイコンを非表示にできます</string>
<string name="set_remote_profile">アプリは全ての投稿を表示するために公開プロフィールにアクセスします。交流する場合は投稿を連合させるために追加の操作を必要とします。</string>
<string name="set_display_reaction_indication">「リアクション」ボタンを表示</string>
<string name="reply_visibility">返信の公開範囲</string>
<string name="local_only">ローカルのみ</string>
<string name="v_list">リスト</string>
<string name="set_display_local_only">「ローカルのみ」ボタンを表示</string>
<string name="translator">翻訳</string>
</resources>

@ -609,7 +609,7 @@
<string name="profiled_updated">Profiel is bijgewerkt!</string>
<string name="select_a_theme">Kies een thema</string>
<string name="types_of_notifications_to_display">Soorten meldingen tonen</string>
<string name="fetch_more_messages">Laad meer berichten…</string>
<string name="fetch_more_messages">Meer laden…</string>
<string name="label_oval">Ovaal</string>
<string name="label_rectangle">Rechthoek</string>
<string name="label_eraser_mode">Wis modus</string>
@ -935,4 +935,22 @@
<string name="mute_tag_action">Negeer tag</string>
<string name="set_display_translate_indication">Vertaalknop altijd tonen</string>
<string name="unpin_tag">Maak tag los</string>
<string name="import_data">Importeren gegevens</string>
<string name="manage_accounts">Beheer accounts</string>
<string name="set_remove_left_margin_title">Verwijder linker kantlijn</string>
<string name="translator">Vertaler</string>
<string name="set_translator">Vertaler</string>
<string name="api_key">Vertaler API key</string>
<string name="version">Versie</string>
<string name="set_translator_version">Vertaler versie</string>
<string name="set_extand_extra_features">Zet optie aan zodat de app meer mogelijkheden laat zien. Dit is voor social softwares zoals Pleroma, Akkoma or Glitch Social</string>
<string name="set_extand_extra_features_title">Extra mogelijkheden</string>
<string name="icons_visibility">Zichtbaarheid iconen</string>
<string name="icons_visibility_summary">Je kan deze iconen onderaan veilig verbergen voor meer ruimte. Ze staan ook in het submenu.</string>
<string name="icons_extra_features">Iconen voor extra mogelijkheden</string>
<string name="icons_extra_features_visibility_summary">Als je instance niet alle mogelijkheden toestaat, kan je deze iconen verbergen</string>
<string name="set_display_quote_indication">Toon de \"Quote\" knop</string>
<string name="set_display_reaction_indication">Toon \"Reacties\" knop</string>
<string name="group_reblogs">Groepeer reblogs in eigen tijdlijn</string>
<string name="set_remove_left_margin">Verwijder linker kantlijn in tijdlijnen voor compactere berichten</string>
</resources>

@ -513,7 +513,7 @@
<string name="unfollow_confirm">Вы хотите отписаться от этого аккаунта?</string>
<string name="set_unfollow_validation">Предупреждать перед отменой подписки</string>
<string name="replace_medium">Replace Medium links</string>
<string name="replace_medium_description">Replace medium.com links with an open source alternative front-end focused on privacy.</string>
<string name="replace_medium_description">Использовать альтернативный интерфейс для Medium</string>
<string name="replace_medium_host">Default: scribe.rip</string>
<string name="set_push_notifications">Использовать систему push-уведомлений для получения уведомлений в режиме реального времени.</string>
<string name="action_add_notes">Добавить примечания</string>
@ -581,4 +581,139 @@
<string name="no_distributors_explanation">Вам нужен дистрибьютор для получения push-уведомлений.\nВы найдете более подробную информацию по адресу %1$s.\n\nВы также можете отключить push-уведомления в настройках для игнорирования этого сообщения.</string>
<string name="select_distributors">Выберите дистрибьютора</string>
<string name="icon_size">Размер значков</string>
<string name="replace_youtube">YouTube</string>
<string name="replace_youtube_host">Домен интерфейса YouTube</string>
<string name="replace_youtube_description">Использовать альтернативный интерфейс для YouTube</string>
<string name="replace_twitter">Twitter</string>
<string name="add_filter">Добавить фильтр</string>
<string name="save_changes">Сохранить изменения</string>
<string name="set_bot_content">Бот-аккаунт</string>
<string name="scheduled">Запланировано</string>
<string name="select_a_theme">Выберите тему</string>
<string name="notif_display_mentions">Упоминания</string>
<string name="notif_display_updates_from_people">Обновления у людей</string>
<string name="delete_field_confirm">Вы уверены, что хотите удалить это поле\?</string>
<string name="also_boosted_by">Также продвинуто:</string>
<string name="admin_scope">Я модератор</string>
<string name="label_oval">Овал</string>
<string name="label_rectangle">Прямоугольник</string>
<string name="label_line">Линия</string>
<string name="label_shape">Фигура</string>
<string name="msg_save_image">Выйти без сохранения изображения\?</string>
<string name="tap_here_to_refresh_poll">Нажмите здесь, чтобы обновить опрос</string>
<string name="refresh_every">Получать уведомления каждые:</string>
<string name="export_settings">Экспорт настроек</string>
<string name="notif_display_reblogs">Репосты</string>
<string name="interactions">Взаимодействия</string>
<string name="delete_field">Удалить поле</string>
<string name="type_of_notifications">Тип уведомлений</string>
<string name="import_settings">Импорт настроек</string>
<string name="pref_theme_base_summary">Выберите, должна ли основа темы быть темной или светлой</string>
<string name="notif_display_favourites">Избранные</string>
<string name="notif_display_poll_results">Результаты опросов</string>
<string name="notif_display_follows">Подписки</string>
<string name="display_all_categories">Отображать все категории</string>
<string name="profiled_updated">Профиль обновлён!</string>
<string name="not_valid_list_name">Недопустимое имя списка!</string>
<string name="no_account_in_list">Для этого списка не найдено ни одного аккаунта!</string>
<string name="release_notes">Список изменений</string>
<string name="default_system_language">Использовать системный язык по умолчанию</string>
<string name="my_app">Мое приложение</string>
<string name="message_has_been_sent">Сообщение отправлено!</string>
<string name="add_field">Добавить поле</string>
<string name="clear_all_notif">Удалить все уведомления</string>
<string name="mark_all_as_read">Пометить все уведомления как прочитанные</string>
<string name="label_eraser_mode">Режим ластика</string>
<string name="customize_timelines">Настроить ленты</string>
<string name="set_display_bookmark_indication">Всегда отображать кнопку закладки</string>
<string name="type_of_notifications_title">Выберите тип уведомлений</string>
<string name="notification_sounds">Звук уведомлений</string>
<string name="disable_notifications">Отключить уведомления</string>
<string name="notifications_are">В это время</string>
<string name="also_favourite_by">"Также в избранном у: "</string>
<string name="my_account">Мой аккаунт</string>
<string name="media_cannot_be_uploaded">Медиафайлы не могут быть загружены!</string>
<string name="set_unfollow_validation_title">Подтвердить отписку</string>
<string name="poll_type">Тип опроса:</string>
<string name="poll_duration">Длительность опроса:</string>
<string name="set_display_translate_indication">Всегда отображать кнопку перевода</string>
<string name="delete_cache">Удалить кеш</string>
<string name="delete_notification_all_warning">Вы уверены, что хотите удалить все уведомления\? Это не может быть отменено.</string>
<string name="load_settings">Загрузить экспортированные настройки</string>
<string name="replace_twitter_description">Использовать альтернативный интерфейс для Twitter</string>
<string name="replace_twitter_host">Домен интерфейса Twitter</string>
<string name="replace_instagram">Instagram</string>
<string name="replace_instagram_description">Использовать альтернативный интерфейс для Instagram</string>
<string name="replace_instagram_host">Домен интерфейса Instagram</string>
<string name="replace_reddit_description">Использовать альтернативный интерфейс для Reddit</string>
<string name="replace_reddit">Reddit</string>
<string name="replace_reddit_host">Домен интерфейса Reddit</string>
<string name="restart">Перезапустить</string>
<string name="report_val3">Это нарушает правила сервера</string>
<string name="report_val_more3">Вы знаете, что это нарушает определенные правила</string>
<string name="change_logo">Изменить логотип</string>
<string name="user">Пользователь</string>
<string name="moderator">Модератор</string>
<string name="report_val1">Мне это не нравится</string>
<string name="set_accounts_page">Количество аккаунтов на загрузку</string>
<string name="load_media_type_title">Загружать предпросмотр</string>
<string name="confirmed">Подтверждено</string>
<string name="unconfirmed">Не подтверждено</string>
<string name="not_valid_tag_name">Недопустимое имя тега!</string>
<string name="set_translator">Переводчик</string>
<string name="set_translator_version">Версия переводчика</string>
<string name="administrator">Администратор</string>
<string name="set_use_cache_indication">Содержимое лент будет временно сохраняться у вас, чтобы ускорить работу приложения.</string>
<string name="display_media">Отображать медиа</string>
<string name="icons_visibility">Видимость иконок</string>
<string name="translator">Переводчик</string>
<string name="api_key">API-ключ переводчика</string>
<string name="set_notifications_page">Количество уведомлений на загрузку</string>
<string name="report_indication_title_status">Скажите нам, что не так с этим постом\?</string>
<string name="report_indication_title_status_more">Выберите наиболее подходящее</string>
<string name="action_unfollow_tag">Отписаться от тега</string>
<string name="action_unfollow_tag_confirm">Вы уверены, что хотите отписаться от этого тега\?</string>
<string name="action_tag_follow">Подписаться на тег</string>
<string name="action_followed_tag_empty">Вы не подписаны ни на один тег!</string>
<string name="unfollow">Отписаться</string>
<string name="followed_tags">Отслеживаемые теги</string>
<string name="follow_tag">Подписаться на тег</string>
<string name="report_val4">Другое</string>
<string name="show_content">Показать содержимое &gt;</string>
<string name="join_the_fediverse">Присоединяйтесь к fediverse</string>
<string name="invite_join_the_fediverse">Привет! Приглашаем Вас присоединиться к Fediverse.</string>
<string name="report_val_more4">Проблема не подпадает под другие категории</string>
<string name="dont_have_an_account">У вас нет аккаунта\?</string>
<string name="my_instance">Мой инстанс</string>
<string name="profiles">Профили</string>
<string name="display_media_notification">Отображать медиа в уведомлениях</string>
<string name="remember_position">Запоминать позицию на ленте</string>
<string name="display_media_notification_summary">Будут отображаться медиа в уведомлениях для репостов и избранного</string>
<string name="version">Версия</string>
<string name="hide_content">Скрыть содержимое &lt;</string>
<string name="report_title">Пожаловаться на %1$s</string>
<string name="report_val_more1">Это НЕ то что вы бы хотели видеть</string>
<string name="set_use_cache">Использовать кэш</string>
<string name="report_val2">Это спам</string>
<string name="report_val_more2">Вредоносные ссылки, повторяющиеся ответы</string>
<string name="about_mastodon">«Mastodon — это не один веб-сайт, такой как Twitter или Facebook, это сеть из тысяч сообществ, управляемых различными организациями и частными лицами, которые обеспечивают бесперебойную работу в социальных сетях».</string>
<string name="change_logo_description">Измените логотип приложения на вашем устройстве</string>
<string name="restart_the_app">Перезапустить приложение\?</string>
<string name="restart_the_app_theme">Вы должны перезапустить приложение, чтобы применить изменения.</string>
<string name="write_the_tag_to_follow">Напишите тег для подписки</string>
<string name="toast_feature_not_supported">Похоже, ваш инстанс не поддерживает эту функцию!</string>
<string name="set_extand_extra_features_title">Дополнительные функции</string>
<string name="set_extand_extra_features">Включив эту опцию, приложение будет отображать дополнительные функции. Это предназначается для социальных платформ, таких как Pleroma, Akkoma или Glitch Social</string>
<string name="icons_visibility_summary">Вы можете смело спрятать эти значки внизу, чтобы было больше места. Они также находятся в подменю.</string>
<string name="cannot_be_empty">Это поле не может быть пустым!</string>
<string name="favourited_by">Добавлен в избранное</string>
<string name="followers_only">Только для подписчиков</string>
<string name="category_music">Музыка</string>
<string name="keepon">Продолжить</string>
<string name="category_custom">Пользовательский</string>
<string name="other">Другое</string>
<string name="data_export_settings_success">Настройки успешно экспортированы</string>
<string name="data_import_settings_success">Настройки успешно импортированы</string>
<string name="data_export_settings">Настройки экспортированы</string>
<string name="boosted_by">Продвинут</string>
</resources>

@ -929,4 +929,25 @@
<string name="import_data">Importa datos</string>
<string name="group_reblogs">Agrupa is cumpartziduras in sa lìnia de tempus printzipale</string>
<string name="manage_accounts">Amministra is contos</string>
<string name="set_extand_extra_features_title">Funtzionalidades extra</string>
<string name="set_post_format">Formadu de publicatzione</string>
<string name="icons_extra_features">Iconas pro funtzionalidades extra</string>
<string name="icons_visibility">Visibilidade de is iconas</string>
<string name="post_format">Formadu de publicatzione</string>
<string name="set_display_quote_indication">Ammustra su butone \"Tzita\"</string>
<string name="set_display_reaction_indication">Ammustra su butone \"Reatziones\"</string>
<string name="set_remote_profile_title">Profilos remotos</string>
<string name="bubble">Bullunca</string>
<string name="v_list">Lista</string>
<string name="following">Sighende</string>
<string name="set_remove_left_margin_title">Boga su màrgine a manca</string>
<string name="set_remove_left_margin">Boga su màrgine a manca de is lìnias de tempus pro chi is messàgios siant prus cumpatos</string>
<string name="translator">Tradutore</string>
<string name="set_translator">Tradutore</string>
<string name="api_key">Crae API de su tradutore</string>
<string name="version">Versione</string>
<string name="set_translator_version">Versione de su tradutore</string>
<string name="set_extand_extra_features">Ativende cussa optzione s\'aplicatzione at a ammustrare funtzionalidades extra. Custa funtzionalidade b\'est pro programmas sotziales che a Pleroma, Akkoma o Glitch Social</string>
<string name="icons_visibility_summary">Podes cuare custas iconas in manera segura in fundu pro tènnere prus logu. Sunt fintzas in su suta-menù.</string>
<string name="icons_extra_features_visibility_summary">Si s\'istàntzia tua no atzetat unas cantas funtzionalidades extra podes cuare custas iconas</string>
</resources>

@ -942,4 +942,24 @@
<string name="set_translator">Çevirmen</string>
<string name="api_key">Çevirmen API anahtarı</string>
<string name="version">Sürüm</string>
<string name="set_extand_extra_features_title">Ek özellikler</string>
<string name="set_extand_extra_features">Bu seçeneği etkinleştirdiğinizde uygulama ek özellikler gösterecektir. Bu özellik Pleroma, Akkoma veya Glitch Social gibi sosyal yazılımlar için yapılır</string>
<string name="post_format">Gönderi biçimi</string>
<string name="set_post_format">Gönderi biçimi</string>
<string name="icons_extra_features">Ek özellikler için simgeler</string>
<string name="icons_extra_features_visibility_summary">Sunucunuz bazı ek özellikleri kabul etmiyorsa, bu simgeleri gizleyebilirsiniz</string>
<string name="set_display_quote_indication">\"Alıntı\" düğmesini göster</string>
<string name="set_display_reaction_indication">\"Tepkiler\" düğmelerini göster</string>
<string name="icons_visibility_summary">Daha fazla alana sahip olmak için bu simgeleri alt kısımda güvenle gizleyebilirsiniz. Ayrıca alt menüde de bulunurlar.</string>
<string name="icons_visibility">Simge görünürlüğü</string>
<string name="exclude_visibility">Hariç tutma görünürlüğü</string>
<string name="v_list">Listele</string>
<string name="following">Takip edilenler</string>
<string name="bubble">Baloncuk</string>
<string name="reply_visibility">Yanıt görünürlüğü</string>
<string name="self">Kendi</string>
<string name="set_remote_profile_title">Uzak profiller</string>
<string name="set_remote_profile">Uygulama, tüm mesajları almak için herkese açık profilleri görüntüleyecektir. Etkileşimlerin mesajları birleştirmek için ek bir adıma ihtiyacı olacaktır.</string>
<string name="set_display_local_only">\"Yalnızca yerel\" düğmesini göster</string>
<string name="local_only">Yalnızca yerel</string>
</resources>

@ -1,3 +1,3 @@
<resources>
<dimen name="fab_margin">200dp</dimen>
<dimen name="fab_margin">48dp</dimen>
</resources>

@ -1,3 +1,3 @@
<resources>
<dimen name="fab_margin">48dp</dimen>
<dimen name="fab_margin">32dp</dimen>
</resources>

@ -770,6 +770,16 @@
<item>text/x.misskeymarkdown</item>
</string-array>
<string-array name="SET_LOCAL_ONLY">
<item>0</item>
<item>1</item>
</string-array>
<string-array name="set_local_only">
<item>No</item>
<item>Yes</item>
</string-array>
<string-array name="set_load_media_type_value">
<item>Always</item>
<item>Wifi only</item>
@ -1361,10 +1371,14 @@
<string name="SET_UNFOLLOW_VALIDATION" translatable="false">SET_UNFOLLOW_VALIDATION</string>
<string name="SET_USE_SINGLE_TOPBAR" translatable="false">SET_USE_SINGLE_TOPBAR</string>
<string name="SET_DISPLAY_COUNTERS" translatable="false">SET_DISPLAY_COUNTERS</string>
<string name="SET_DISPLAY_COMPACT_ACTION_BUTTON" translatable="false">SET_DISPLAY_COMPACT_ACTION_BUTTON</string>
<string name="SET_TIMELINES_IN_A_LIST" translatable="false">SET_TIMELINES_IN_A_LIST</string>
<string name="SET_LED_COLOUR_VAL_N" translatable="false">SET_LED_COLOUR_VAL_N</string>
<string name="SET_SHOW_BOOSTS" translatable="false">SET_SHOW_BOOSTS</string>
<string name="SET_SHOW_REPLIES" translatable="false">SET_SHOW_REPLIES</string>
<string name="SET_DISABLE_ANIMATED_EMOJI" translatable="false">SET_DISABLE_ANIMATED_EMOJI</string>
<string name="SET_CAPITALIZE" translatable="false">SET_CAPITALIZE</string>
<string name="SET_THEME_BASE" translatable="false">SET_THEME_BASE</string>
@ -1428,11 +1442,13 @@
<string name="SET_FILTER_REGEX_PUBLIC" translatable="false">SET_FILTER_REGEX_PUBLIC</string>
<string name="SET_NOTIF_VALIDATION" translatable="false">SET_NOTIF_VALIDATION</string>
<string name="SET_DISPLAY_BOOKMARK" translatable="false">SET_DISPLAY_BOOKMARK</string>
<string name="SET_PIXELFED_PRESENTATION" translatable="false">SET_PIXELFED_PRESENTATION</string>
<string name="SET_DISPLAY_QUOTES" translatable="false">SET_DISPLAY_QUOTES</string>
<string name="SET_DISPLAY_REACTIONS" translatable="false">SET_DISPLAY_REACTIONS</string>
<string name="SET_DISPLAY_TRANSLATE" translatable="false">SET_DISPLAY_TRANSLATE</string>
<string name="SET_POST_FORMAT" translatable="false">SET_POST_FORMAT</string>
<string name="SET_COMPOSE_LOCAL_ONLY" translatable="false">SET_COMPOSE_LOCAL_ONLY</string>
<string name="SET_TRANSLATOR" translatable="false">SET_TRANSLATOR</string>
<string name="SET_TRANSLATOR_VERSION" translatable="false">SET_TRANSLATOR_VERSION</string>
@ -1443,8 +1459,10 @@
<string name="SET_NOTIF_VALIDATION_FAV" translatable="false">SET_NOTIF_VALIDATION_FAV</string>
<string name="SET_DISPLAY_COUNTER_FAV_BOOST" translatable="false">SET_DISPLAY_COUNTER_FAV_BOOST</string>
<string name="SET_REMOVE_LEFT_MARGIN" translatable="false">SET_REMOVE_LEFT_MARGIN</string>
<string name="SET_EXTAND_EXTRA_FEATURES" translatable="false">SET_EXTAND_EXTRA_FEATURES</string>
<string name="SET_PROFILE_REMOTELY" translatable="false">SET_PROFILE_REMOTELY</string>
<string name="SET_EXTAND_EXTRA_FEATURES" translatable="false">SET_EXTAND_EXTRA_FEATURES</string>
<string name="SET_DISPLAY_LOCAL_ONLY" translatable="false">SET_DISPLAY_LOCAL_ONLY</string>
<string name="SET_INNER_MARKER" translatable="false">SET_INNER_MARKER</string>
<string name="SET_NOTIF_SILENT" translatable="false">SET_NOTIF_SILENT</string>
<string name="SET_REMEMBER_POSITION" translatable="false">SET_REMEMBER_POSITION</string>
@ -2174,4 +2192,17 @@
<string name="icons_extra_features_visibility_summary">If your instance does not accept some extra features, you can hide these icons</string>
<string name="set_display_quote_indication">Display the \"Quote\" button</string>
<string name="set_display_reaction_indication">Display \"Reactions\" buttons</string>
<string name="bubble">Bubble</string>
<string name="exclude_visibility">Exclude visibility</string>
<string name="reply_visibility">Reply visibility</string>
<string name="v_list">List</string>
<string name="following">Following</string>
<string name="self">Self</string>
<string name="set_remote_profile_title">Remote profiles</string>
<string name="set_remote_profile">The app will display publicly profiles to get all messages. Interactions will need an extra step to federate messages.</string>
<string name="local_only">Local only</string>
<string name="set_display_local_only">Display \"Local only\" button</string>
<string name="set_pixelfed_presentation">Pixelfed presentation for media</string>
<string name="set_display_compact_buttons">Compact action buttons</string>
<string name="set_display_compact_buttons_description">Buttons at the bottom of messages will not take the whole width</string>
</resources>

@ -47,6 +47,12 @@
app:key="@string/SET_DISPLAY_REACTIONS"
app:singleLineTitle="false"
app:title="@string/set_display_reaction_indication" />
<SwitchPreferenceCompat
android:defaultValue="true"
app:iconSpaceReserved="false"
app:key="@string/SET_DISPLAY_LOCAL_ONLY"
app:singleLineTitle="false"
app:title="@string/set_display_local_only" />
</app.fedilab.android.helper.settings.LongSummaryPreferenceCategory>
<ListPreference
@ -61,4 +67,15 @@
app:title="@string/set_post_format"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="0"
app:dependency="@string/SET_EXTAND_EXTRA_FEATURES"
app:dialogTitle="@string/local_only"
app:entries="@array/set_local_only"
app:entryValues="@array/SET_LOCAL_ONLY"
app:iconSpaceReserved="false"
app:key="@string/SET_COMPOSE_LOCAL_ONLY"
app:title="@string/local_only"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceScreen>

@ -28,6 +28,14 @@
app:summary="@string/set_remove_left_margin"
app:title="@string/set_remove_left_margin_title" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="@string/SET_PROFILE_REMOTELY"
app:singleLineTitle="false"
app:summary="@string/set_remote_profile"
app:title="@string/set_remote_profile_title" />
<SwitchPreferenceCompat
app:defaultValue="true"
@ -45,6 +53,14 @@
app:summary="@string/set_display_counters_description"
app:title="@string/set_display_counters" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="@string/SET_DISPLAY_COMPACT_ACTION_BUTTON"
app:singleLineTitle="false"
app:summary="@string/set_display_compact_buttons_description"
app:title="@string/set_display_compact_buttons" />
<SwitchPreferenceCompat
app:defaultValue="true"
app:iconSpaceReserved="false"

@ -46,7 +46,12 @@
app:key="@string/SET_DISPLAY_BOOKMARK"
app:singleLineTitle="false"
app:title="@string/set_display_bookmark_indication" />
<SwitchPreferenceCompat
android:defaultValue="false"
app:iconSpaceReserved="false"
app:key="@string/SET_PIXELFED_PRESENTATION"
app:singleLineTitle="false"
app:title="@string/set_pixelfed_presentation" />
<SwitchPreferenceCompat
android:defaultValue="false"
app:iconSpaceReserved="false"

@ -2,11 +2,11 @@ apply plugin: 'com.android.library'
android {
compileSdkVersion 31
compileSdkVersion 33
defaultConfig {
minSdkVersion 15
targetSdkVersion 31
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -29,9 +29,9 @@ configurations {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
//noinspection GradleCompatible
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat:1.5.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}

@ -0,0 +1,2 @@
- Fehlerbehebungen
- Ermöglicht das Teilen aus der App

@ -0,0 +1,6 @@
- Weiteres Verbessern des Scrollverhaltens
- Scrollen nach oben (erneute Auswahl des Tabs) holt neue Nachrichten und scrollt dann nach oben
- Fokuspunkt für die Medienvorschau entfernen
- Behebung des Problems, dass Nachrichten nicht mit einem Konto geteilt werden können
- Behebung des schwarzen Themas
- Behebung einiger Schaltflächenfarben

@ -0,0 +1,5 @@
- Behebung eines Problems mit dem Cache und der Home-Timeline
- Nitter-Zeitleisten verwenden die benutzerdefinierte Instanz aus den Einstellungen
- Behebung von Nitter-Problemen (nur RT)
- Ungültiges Zertifikat für Onion-URLs (Google) wird nicht mehr akzeptiert
- Behebung einiger Abstürze

@ -0,0 +1,4 @@
- Neuer Cache-Mechanismus
- Cache kann in den Einstellungen deaktiviert werden
- Hinzufügen von Zählern für neue Nachrichten
- Einige Korrekturen.

@ -0,0 +1,3 @@
- Fehlerkorrektur für Beiträge die Medien enthalten
- Ladeverhalten der Thumbnails einstellen: Immer/Nur WLAN/Fragen
- Einige andere kleine Korrekturen

@ -0,0 +1,5 @@
- Korrektur der Beitragsnachricht mit Medien
- Kodierungsproblem bei Medienbeschreibungen behoben
- Behebung der Friendica-Tag-Suche
- Korrekturen beim Verfassen
- Einige andere kleine Korrekturen

@ -0,0 +1,10 @@
Neu hinzugefügt:
- Zwischenspeicher-Mechanismus (kann in den Einstellungen deaktiviert werden)
- Ladeverhalten für Vorschau einstellbar (Immer/Nur mit WLan/Fragen)
- Zähler für neue Nachrichten in Zeitleisten verfügbar
Behoben:
- Kontextmenü funktioniert nicht in Threads
- Problem bei Tag-Suche mit Friendica
- Klick auf Benachrichtigung öffnet den falschen Tab
- Kodierungsproblem bei der Beschreibung von Medien
- Einige andere Korrekturen

@ -0,0 +1,7 @@
Hinzugefügt:
- Zähler in den Einstellungen deaktivieren
Behoben:
- Doppelte Nachrichten aus dem Cache
- Benachrichtigungen im Doppel
- Entwürfe werden nicht automatisch entfernt
- Nachrichten werden nach Löschung nicht aus dem Cache entfernt

@ -0,0 +1,2 @@
Behoben:
- Fehler beim Anhängen von Medien an eine Antwort

@ -0,0 +1,12 @@
Geändert:
- Swipe zwischen Zeitleisten
- Cache verbessern
- Schaltflächengrößen in Nachrichten folgen definierter Skala
Behoben:
- Pleroma: Emoji-Reaktionen
- Teilen (mehrere Korrekturen)
- Theme-Probleme
- Rendering-Problem für Links
- Benachrichtigungen werden nicht aus dem Cache entfernt
- Einige Abstürze

@ -0,0 +1,15 @@
Geändert:
- Swipe zwischen Zeitleisten
- Cache verbessern
- Die Größe der Schaltflächen kann in den Einstellungen geändert werden
- Französische Übersetzung
Behoben:
- Pleroma: Emoji-Reaktionen
- Teilen (mehrere Korrekturen)
- Theme-Probleme
- Rendering-Problem für Links
- Benachrichtigungen werden nicht aus dem Cache entfernt
- Problem mit Wasserzeichen
- Paginierung mit Lesezeichen/Favoriten
- Einige Abstürze

@ -0,0 +1,9 @@
Hinzugefügt:
- DeepL Übersetzung unterstützt Free/Pro-Tasten
Geändert:
- Schaltflächen für Medien beim Bearbeiten ausblenden
Behoben:
- GIFs werden als statische Bilder geladen
- Vorgeschlagene Konten können nicht verfolgt werden

@ -0,0 +1,10 @@
Geändert:
- Ausgeblendete Medien mit Vorschaubildern
Behoben:
- Problem mit Medien für Android 11+
- Absturz, wenn kein Übersetzungsschlüssel gesetzt wurde
- Behoben DeepL für API pro
- Absturz beim Besuch eines Profils mit vielen Medien
- Stummgeschaltete Konten funktionieren nicht ohne Filter
- Animierte benutzerdefinierte Emoji werden nicht angezeigt

@ -0,0 +1,14 @@
Hinzugefügt:
- Zusätzliche Funktionen in den Einstellungen
- Anpassbare Einstellungen für zusätzliche Funktionen
- Unterstützung von Zitaten, Reaktionen mit Nachrichten
- Unterstützung von Textformaten (html, markdown, etc.) beim Verfassen
Geändert:
- Kontextmenü bei langem Drücken eines Links
- Lesezeichen und Übersetzungsschaltflächen werden jetzt pro Konto angezeigt
Behoben:
- CW funktioniert nicht mit Medien
- Medien werden bei älteren Instanzen nicht angezeigt
- Einige Abstürze

@ -0,0 +1,10 @@
Added:
- Add Bubble timeline support in extra-features with filters
- Allow to display public profiles by default to get all messages (Settings > Interface)
Changed:
- Full rework on links in messages (also mentions and tags)
Fixed:
- Spoiler text when editing
- Fix watermarks

@ -0,0 +1,5 @@
Added:
- Glitch: Allow to post messages locally (Can be turned off in Settings)
Fixed:
- Crashes

@ -0,0 +1,8 @@
Fixed:
- Cross-compose: Wrong instance emojis
- Custom emojis not displayed in notifications
- Fav/Boost markers with shared messages
- Empty notifications
- Fix cw removed when replying
- Fix expand media with fit preview images when sensitive
- Fix an issue with fetch more displayed too often (cache clear will help or wait new messages)

@ -0,0 +1,13 @@
Added:
- Pixelfed: Custom layout to display Media fully
*(Settings > Timelines > Pixelfed Presentation) - Also works for other softwares when there are media
Changed:
- Add pinned tag in "any" to avoid to lose it when renaming timeline
Fixed:
- Fix push notifications with several accounts
- Fix quotes with tags/mentions
- Fix notifications
- Fix sending multiple media
- Some crashes

@ -0,0 +1,21 @@
Added:
- Add Bubble timeline support in extra-features with filters
- Allow to display public profiles by default to get all messages (Settings > Interface)
- Glitch: Allow to post messages locally (Can be turned off in Settings)
- Pixelfed: Custom layout to display Media fully (Also works for other software when there are media)
- Allow to align left action buttons in messages
Changed:
- Full rework on links in messages (also mentions and tags)
- Add pinned tag in "any" to avoid to lose it when renaming timeline
Fixed:
- Links to messages not handled by the app
- CW when editing a message
- Fix push notifications with several accounts
- New messages or edition notifications not pushed
- Fix quotes with tags/mentions
- Fix notifications
- Fix sending multiple media
- Fix crashes
Loading…
Cancel
Save