Merge branch 'develop' into main

This commit is contained in:
Thomas 2022-07-19 18:39:03 +02:00
commit 92a9479c95
85 changed files with 2866 additions and 2098 deletions

View file

@ -1,7 +1,7 @@
[![Translation status](https://hosted.weblate.org/widgets/fedilab/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/fedilab/)
   [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
# Fedilab is a multi-accounts client for Mastodon, Pleroma, Peertube, GNU Social, Friendica and Pixelfed
# Fedilab is a multi-accounts client for Mastodon, Pleroma, Friendica and Pixelfed
## Donate
@ -16,7 +16,7 @@
[WIKI](https://fedilab.app/wiki/home/)
[Release notes](https://framagit.org/tom79/fedilab/tags)
[Release notes](https://codeberg.org/tom79/Fedilab/tags)
Lead developer: [toot.fedilab.app/@apps](https://toot.fedilab.app/@apps)

View file

@ -9,8 +9,8 @@ android {
defaultConfig {
minSdk 21
targetSdk 31
versionCode 396
versionName "3.0.6"
versionCode 399
versionName "3.0.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
flavorDimensions "default"
@ -85,11 +85,9 @@ dependencies {
implementation 'com.github.GrenderG:Toasty:1.5.2'
implementation 'org.framagit.tom79:SparkButton:1.0.13'
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation "com.github.bumptech.glide:okhttp3-integration:4.12.0"
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation ("com.github.bumptech.glide:recyclerview-integration:4.12.0") {
// Excludes the support library because it's already included by Glide.
transitive = false
}
implementation project(path: ':mytransl')
implementation project(path: ':ratethisapp')
@ -99,8 +97,7 @@ dependencies {
implementation project(path: ':cropper')
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.penfeizhou.android.animation:apng:2.22.0'
implementation 'com.github.penfeizhou.android.animation:gif:2.22.0'
implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.22.0'
implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'com.github.piasy:rxandroidaudio:1.7.0'

View file

@ -0,0 +1,730 @@
[
{
"code": "aa",
"language": "Afaraf"
},
{
"code": "ab",
"language": "аҧсуа бызшәа"
},
{
"code": "ae",
"language": "avesta"
},
{
"code": "af",
"language": "Afrikaans"
},
{
"code": "ak",
"language": "Akan"
},
{
"code": "am",
"language": "አማርኛ"
},
{
"code": "an",
"language": "aragonés"
},
{
"code": "ar",
"language": "اللغة العربية"
},
{
"code": "as",
"language": "অসমীয়া"
},
{
"code": "av",
"language": "авар мацӀ"
},
{
"code": "ay",
"language": "aymar aru"
},
{
"code": "az",
"language": "azərbaycan dili"
},
{
"code": "ba",
"language": "башҡорт теле"
},
{
"code": "be",
"language": "беларуская мова"
},
{
"code": "bg",
"language": "български език"
},
{
"code": "bh",
"language": "भोजपुरी"
},
{
"code": "bi",
"language": "Bislama"
},
{
"code": "bm",
"language": "bamanankan"
},
{
"code": "bn",
"language": "বাংলা"
},
{
"code": "bo",
"language": "བོད་ཡིག"
},
{
"code": "br",
"language": "brezhoneg"
},
{
"code": "bs",
"language": "bosanski jezik"
},
{
"code": "ca",
"language": "Català"
},
{
"code": "ce",
"language": "нохчийн мотт"
},
{
"code": "ch",
"language": "Chamoru"
},
{
"code": "co",
"language": "corsu"
},
{
"code": "cr",
"language": "ᓀᐦᐃᔭᐍᐏᐣ"
},
{
"code": "cs",
"language": "čeština"
},
{
"code": "cu",
"language": "ѩзыкъ словѣньскъ"
},
{
"code": "cv",
"language": "чӑваш чӗлхи"
},
{
"code": "cy",
"language": "Cymraeg"
},
{
"code": "da",
"language": "dansk"
},
{
"code": "de",
"language": "Deutsch"
},
{
"code": "dv",
"language": "Dhivehi"
},
{
"code": "dz",
"language": "རྫོང་ཁ"
},
{
"code": "ee",
"language": "Eʋegbe"
},
{
"code": "el",
"language": "Ελληνικά"
},
{
"code": "en",
"language": "English"
},
{
"code": "eo",
"language": "Esperanto"
},
{
"code": "es",
"language": "Español"
},
{
"code": "et",
"language": "eesti"
},
{
"code": "eu",
"language": "euskara"
},
{
"code": "fa",
"language": "فارسی"
},
{
"code": "ff",
"language": "Fulfulde"
},
{
"code": "fi",
"language": "suomi"
},
{
"code": "fj",
"language": "Vakaviti"
},
{
"code": "fo",
"language": "føroyskt"
},
{
"code": "fr",
"language": "Français"
},
{
"code": "fy",
"language": "Frysk"
},
{
"code": "ga",
"language": "Gaeilge"
},
{
"code": "gd",
"language": "Gàidhlig"
},
{
"code": "gl",
"language": "galego"
},
{
"code": "gu",
"language": "ગુજરાતી"
},
{
"code": "gv",
"language": "Gaelg"
},
{
"code": "ha",
"language": "هَوُسَ"
},
{
"code": "he",
"language": "עברית"
},
{
"code": "hi",
"language": "हिन्दी"
},
{
"code": "ho",
"language": "Hiri Motu"
},
{
"code": "hr",
"language": "Hrvatski"
},
{
"code": "ht",
"language": "Kreyòl ayisyen"
},
{
"code": "hu",
"language": "magyar"
},
{
"code": "hy",
"language": "Հայերեն"
},
{
"code": "hz",
"language": "Otjiherero"
},
{
"code": "ia",
"language": "Interlingua"
},
{
"code": "id",
"language": "Bahasa Indonesia"
},
{
"code": "ie",
"language": "Interlingue"
},
{
"code": "ig",
"language": "Asụsụ Igbo"
},
{
"code": "ii",
"language": "ꆈꌠ꒿ Nuosuhxop"
},
{
"code": "ik",
"language": "Iñupiaq"
},
{
"code": "io",
"language": "Ido"
},
{
"code": "is",
"language": "Íslenska"
},
{
"code": "it",
"language": "Italiano"
},
{
"code": "iu",
"language": "ᐃᓄᒃᑎᑐᑦ"
},
{
"code": "ja",
"language": "日本語"
},
{
"code": "jv",
"language": "basa Jawa"
},
{
"code": "ka",
"language": "ქართული"
},
{
"code": "kg",
"language": "Kikongo"
},
{
"code": "ki",
"language": "Gĩkũyũ"
},
{
"code": "kj",
"language": "Kuanyama"
},
{
"code": "kk",
"language": "қазақ тілі"
},
{
"code": "kl",
"language": "kalaallisut"
},
{
"code": "km",
"language": "ខេមរភាសា"
},
{
"code": "kn",
"language": "ಕನ್ನಡ"
},
{
"code": "ko",
"language": "한국어"
},
{
"code": "kr",
"language": "Kanuri"
},
{
"code": "ks",
"language": "कश्मीरी"
},
{
"code": "ku",
"language": "Kurmancî"
},
{
"code": "kv",
"language": "коми кыв"
},
{
"code": "kw",
"language": "Kernewek"
},
{
"code": "ky",
"language": "Кыргызча"
},
{
"code": "la",
"language": "latine"
},
{
"code": "lb",
"language": "Lëtzebuergesch"
},
{
"code": "lg",
"language": "Luganda"
},
{
"code": "li",
"language": "Limburgs"
},
{
"code": "ln",
"language": "Lingála"
},
{
"code": "lo",
"language": "ພາສາ"
},
{
"code": "lt",
"language": "lietuvių kalba"
},
{
"code": "lu",
"language": "Tshiluba"
},
{
"code": "lv",
"language": "latviešu valoda"
},
{
"code": "mg",
"language": "fiteny malagasy"
},
{
"code": "mh",
"language": "Kajin M̧ajeļ"
},
{
"code": "mi",
"language": "te reo Māori"
},
{
"code": "mk",
"language": "македонски јазик"
},
{
"code": "ml",
"language": "മലയാളം"
},
{
"code": "mn",
"language": "Монгол хэл"
},
{
"code": "mr",
"language": "मराठी"
},
{
"code": "ms",
"language": "Bahasa Melayu"
},
{
"code": "mt",
"language": "Malti"
},
{
"code": "my",
"language": "ဗမာစာ"
},
{
"code": "na",
"language": "Ekakairũ Naoero"
},
{
"code": "nb",
"language": "Norsk bokmål"
},
{
"code": "nd",
"language": "isiNdebele"
},
{
"code": "ne",
"language": "नेपाली"
},
{
"code": "ng",
"language": "Owambo"
},
{
"code": "nl",
"language": "Nederlands"
},
{
"code": "nn",
"language": "Norsk Nynorsk"
},
{
"code": "no",
"language": "Norsk"
},
{
"code": "nr",
"language": "isiNdebele"
},
{
"code": "nv",
"language": "Diné bizaad"
},
{
"code": "ny",
"language": "chiCheŵa"
},
{
"code": "oc",
"language": "occitan"
},
{
"code": "oj",
"language": "ᐊᓂᔑᓈᐯᒧᐎᓐ"
},
{
"code": "om",
"language": "Afaan Oromoo"
},
{
"code": "or",
"language": "ଓଡ଼ିଆ"
},
{
"code": "os",
"language": "ирон æвзаг"
},
{
"code": "pa",
"language": "ਪੰਜਾਬੀ"
},
{
"code": "pi",
"language": "पाऴि"
},
{
"code": "pl",
"language": "Polski"
},
{
"code": "ps",
"language": "پښتو"
},
{
"code": "pt",
"language": "Português"
},
{
"code": "qu",
"language": "Runa Simi"
},
{
"code": "rm",
"language": "rumantsch grischun"
},
{
"code": "rn",
"language": "Ikirundi"
},
{
"code": "ro",
"language": "Română"
},
{
"code": "ru",
"language": "Русский"
},
{
"code": "rw",
"language": "Ikinyarwanda"
},
{
"code": "sa",
"language": "संस्कृतम्"
},
{
"code": "sc",
"language": "sardu"
},
{
"code": "sd",
"language": "सिन्धी"
},
{
"code": "se",
"language": "Davvisámegiella"
},
{
"code": "sg",
"language": "yângâ tî sängö"
},
{
"code": "si",
"language": "සිංහල"
},
{
"code": "sk",
"language": "slovenčina"
},
{
"code": "sl",
"language": "slovenščina"
},
{
"code": "sn",
"language": "chiShona"
},
{
"code": "so",
"language": "Soomaaliga"
},
{
"code": "sq",
"language": "Shqip"
},
{
"code": "sr",
"language": "српски језик"
},
{
"code": "ss",
"language": "SiSwati"
},
{
"code": "st",
"language": "Sesotho"
},
{
"code": "su",
"language": "Basa Sunda"
},
{
"code": "sv",
"language": "Svenska"
},
{
"code": "sw",
"language": "Kiswahili"
},
{
"code": "ta",
"language": "தமிழ்"
},
{
"code": "te",
"language": "తెలుగు"
},
{
"code": "tg",
"language": "тоҷикӣ"
},
{
"code": "th",
"language": "ไทย"
},
{
"code": "ti",
"language": "ትግርኛ"
},
{
"code": "tk",
"language": "Türkmen"
},
{
"code": "tl",
"language": "Wikang Tagalog"
},
{
"code": "tn",
"language": "Setswana"
},
{
"code": "to",
"language": "faka Tonga"
},
{
"code": "tr",
"language": "Türkçe"
},
{
"code": "ts",
"language": "Xitsonga"
},
{
"code": "tt",
"language": "татар теле"
},
{
"code": "tw",
"language": "Twi"
},
{
"code": "ty",
"language": "Reo Tahiti"
},
{
"code": "ug",
"language": "ئۇيغۇرچە‎"
},
{
"code": "uk",
"language": "Українська"
},
{
"code": "ur",
"language": "اردو"
},
{
"code": "uz",
"language": "Ўзбек"
},
{
"code": "ve",
"language": "Tshivenḓa"
},
{
"code": "vi",
"language": "Tiếng Việt"
},
{
"code": "vo",
"language": "Volapük"
},
{
"code": "wa",
"language": "walon"
},
{
"code": "wo",
"language": "Wollof"
},
{
"code": "xh",
"language": "isiXhosa"
},
{
"code": "yi",
"language": "ייִדיש"
},
{
"code": "yo",
"language": "Yorùbá"
},
{
"code": "za",
"language": "Saɯ cueŋƅ"
},
{
"code": "zh",
"language": "中文"
},
{
"code": "zu",
"language": "isiZulu"
}
]

View file

@ -8,7 +8,7 @@
"accent": "#FF2b90d9",
"accent_dark": "#FF1b80c9",
"accent_light": "#FF772b90d9",
"background": "#FF121212",
"background": "#FF272727",
"background_dark": "#FF282c37",
"background_light": "#FF282c37",
"should_tint_statusbar": true,

View file

@ -537,7 +537,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
headerMainBinding.ownerAccounts.setImageResource(R.drawable.ic_baseline_arrow_drop_up_24);
new Thread(() -> {
try {
List<BaseAccount> accounts = new Account(BaseMainActivity.this).getAll();
List<BaseAccount> accounts = new Account(BaseMainActivity.this).getCrossAccounts();
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
binding.navView.getMenu().clear();
@ -772,7 +772,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
currentAccount.mastodon_account.display_name = currentAccount.mastodon_account.acct;
}
headerMainBinding.accountName.setText(currentAccount.mastodon_account.display_name);
Helper.loadPP(headerMainBinding.accountProfilePicture, currentAccount);
Helper.loadPP(headerMainBinding.accountProfilePicture, currentAccount, false);
MastodonHelper.loadProfileMediaMastodon(headerMainBinding.backgroundImage, currentAccount.mastodon_account, MastodonHelper.MediaAccountType.HEADER);
/*
* Some general data are loaded when the app starts such;
@ -786,7 +786,12 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
new ViewModelProvider(BaseMainActivity.this).get(InstancesVM.class).getEmoji(currentInstance);
//Retrieve instance info
new ViewModelProvider(BaseMainActivity.this).get(InstancesVM.class).getInstance(currentInstance)
.observe(BaseMainActivity.this, instance -> instanceInfo = instance.info);
.observe(BaseMainActivity.this, instance -> {
instanceInfo = instance.info;
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(getString(R.string.INSTANCE_INFO) + MainActivity.currentInstance, Instance.serialize(instanceInfo));
editor.apply();
});
//Retrieve filters
new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class).getFilters(currentInstance, currentToken)
.observe(BaseMainActivity.this, filters -> mainFilters = filters);
@ -850,7 +855,7 @@ 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));
if (emojis == null || !emojis.containsKey(BaseMainActivity.currentInstance)) {
if (emojis == null || !emojis.containsKey(BaseMainActivity.currentInstance) || emojis.get(BaseMainActivity.currentInstance) == null) {
new Thread(() -> {
try {
emojis.put(currentInstance, new EmojiInstance(BaseMainActivity.this).getEmojiList(BaseMainActivity.currentInstance));
@ -1059,11 +1064,6 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
}
@Override
protected void onResume() {
super.onResume();
}
public void redrawPinned(List<MastodonList> mastodonLists) {
int currentItem = binding.viewPager.getCurrentItem();
new ViewModelProvider(BaseMainActivity.this).get(TopBarVM.class).getDBPinned()

View file

@ -22,8 +22,6 @@ import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
@ -46,6 +44,7 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -105,14 +104,7 @@ public class AdminAccountActivity extends BaseActivity {
}
binding.toolbar.setPopupTheme(Helper.popupStyle());
if (account != null) {
new Thread(() -> {
account = SpannableHelper.convertAccount(AdminAccountActivity.this, account);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> initializeView(account);
mainHandler.post(myRunnable);
}).start();
initializeView(account);
} else {
Toasty.error(AdminAccountActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
finish();
@ -314,7 +306,10 @@ public class AdminAccountActivity extends BaseActivity {
}
binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE);
binding.accountDn.setText(
account.getSpanDisplayName(AdminAccountActivity.this,
new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", account.acct));
binding.accountUn.setOnLongClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);

View file

@ -22,8 +22,6 @@ import android.content.SharedPreferences;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
@ -46,6 +44,7 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -105,14 +104,7 @@ public class AdminReportActivity extends BaseActivity {
}
binding.toolbar.setPopupTheme(Helper.popupStyle());
if (account != null) {
new Thread(() -> {
account = SpannableHelper.convertAccount(AdminReportActivity.this, account);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> initializeView(account);
mainHandler.post(myRunnable);
}).start();
initializeView(account);
} else {
Toasty.error(AdminReportActivity.this, getString(R.string.toast_error_loading_account), Toast.LENGTH_LONG).show();
finish();
@ -331,8 +323,10 @@ public class AdminReportActivity extends BaseActivity {
binding.accountMoved.setMovementMethod(LinkMovementMethod.getInstance());
}
binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE);
binding.accountDn.setText(
account.getSpanDisplayName(AdminReportActivity.this,
new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", account.acct));
binding.accountUn.setOnLongClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);

View file

@ -26,6 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
@ -48,6 +49,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
@ -69,10 +71,12 @@ import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Context;
import app.fedilab.android.client.entities.api.EmojiInstance;
import app.fedilab.android.client.entities.api.Instance;
import app.fedilab.android.client.entities.api.Mention;
import app.fedilab.android.client.entities.api.ScheduledStatus;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.BaseAccount;
import app.fedilab.android.client.entities.app.Languages;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.databinding.ActivityPaginationBinding;
import app.fedilab.android.databinding.PopupContactBinding;
@ -81,7 +85,6 @@ import app.fedilab.android.helper.DividerDecorationSimple;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.MediaHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.interfaces.OnDownloadInterface;
import app.fedilab.android.jobs.ScheduleThreadWorker;
@ -100,7 +103,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
public static final int REQUEST_AUDIO_PERMISSION_RESULT = 1653;
public static final int PICK_MEDIA = 5700;
public static final int TAKE_PHOTO = 5600;
private final Timer timer = new Timer();
private List<Status> statusList;
private Status statusReply, statusMention;
private StatusDraft statusDraft;
@ -234,6 +237,14 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
}).start();
}
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ComposeActivity.this);
if (MainActivity.instanceInfo == null) {
String instanceInfo = sharedpreferences.getString(getString(R.string.INSTANCE_INFO) + instance, null);
if (instanceInfo != null) {
MainActivity.instanceInfo = Instance.restore(instanceInfo);
}
}
StatusesVM statusesVM = new ViewModelProvider(ComposeActivity.this).get(StatusesVM.class);
//Empty compose
List<Status> statusDraftList = new ArrayList<>();
@ -257,86 +268,70 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
});
} else if (statusDraft != null) {//Restore a draft with all messages
new Thread(() -> {
if (statusDraft.statusReplyList != null) {
statusDraft.statusReplyList = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusDraft.statusReplyList);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
if (statusDraft.statusReplyList != null) {
statusList.addAll(statusDraft.statusReplyList);
binding.recyclerView.addItemDecoration(new DividerDecorationSimple(ComposeActivity.this, statusList));
}
int statusCount = statusList.size();
statusList.addAll(statusDraft.statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
binding.recyclerView.scrollToPosition(composeAdapter.getItemCount() - 1);
};
mainHandler.post(myRunnable);
}).start();
if (statusDraft.statusReplyList != null) {
statusList.addAll(statusDraft.statusReplyList);
binding.recyclerView.addItemDecoration(new DividerDecorationSimple(ComposeActivity.this, statusList));
}
int statusCount = statusList.size();
statusList.addAll(statusDraft.statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
binding.recyclerView.scrollToPosition(composeAdapter.getItemCount() - 1);
} else if (statusReply != null) {
new Thread(() -> {
statusReply = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusReply);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
statusList.add(statusReply);
int statusCount = statusList.size();
statusDraftList.get(0).in_reply_to_id = statusReply.id;
//We change order for mentions
//At first place the account that has been mentioned if it's not our
statusDraftList.get(0).mentions = new ArrayList<>();
if (!statusReply.account.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
Mention mention = new Mention();
mention.acct = "@" + statusReply.account.acct;
mention.url = statusReply.account.url;
mention.username = statusReply.account.username;
statusDraftList.get(0).mentions.add(mention);
}
statusList.add(statusReply);
int statusCount = statusList.size();
statusDraftList.get(0).in_reply_to_id = statusReply.id;
//We change order for mentions
//At first place the account that has been mentioned if it's not our
statusDraftList.get(0).mentions = new ArrayList<>();
if (statusReply.account.acct != null && !statusReply.account.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
Mention mention = new Mention();
mention.acct = "@" + statusReply.account.acct;
mention.url = statusReply.account.url;
mention.username = statusReply.account.username;
statusDraftList.get(0).mentions.add(mention);
}
//There are other mentions to
if (statusReply.mentions != null && statusReply.mentions.size() > 0) {
for (Mention mentionTmp : statusReply.mentions) {
if (!mentionTmp.acct.equalsIgnoreCase(statusReply.account.acct) && !mentionTmp.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
statusDraftList.get(0).mentions.add(mentionTmp);
}
}
//There are other mentions to
if (statusReply.mentions != null && statusReply.mentions.size() > 0) {
for (Mention mentionTmp : statusReply.mentions) {
if (statusReply.account.acct != null && !mentionTmp.acct.equalsIgnoreCase(statusReply.account.acct) && !mentionTmp.acct.equalsIgnoreCase(currentAccount.mastodon_account.acct)) {
statusDraftList.get(0).mentions.add(mentionTmp);
}
if (mentionBooster != null) {
Mention mention = new Mention();
mention.acct = mentionBooster.acct;
mention.url = mentionBooster.url;
mention.username = mentionBooster.username;
boolean present = false;
for (Mention mentionTmp : statusDraftList.get(0).mentions) {
if (mentionTmp.acct.equalsIgnoreCase(mentionBooster.acct)) {
present = true;
break;
}
}
if (!present) {
statusDraftList.get(0).mentions.add(mention);
}
}
}
if (mentionBooster != null) {
Mention mention = new Mention();
mention.acct = mentionBooster.acct;
mention.url = mentionBooster.url;
mention.username = mentionBooster.username;
boolean present = false;
for (Mention mentionTmp : statusDraftList.get(0).mentions) {
if (mentionTmp.acct.equalsIgnoreCase(mentionBooster.acct)) {
present = true;
break;
}
if (statusReply.spoiler_text != null) {
statusDraftList.get(0).spoiler_text = statusReply.spoiler_text;
}
//StatusDraftList at this point should only have one element
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
statusesVM.getContext(currentInstance, BaseMainActivity.currentToken, statusReply.id)
.observe(ComposeActivity.this, this::initializeContextView);
};
mainHandler.post(myRunnable);
}).start();
}
if (!present) {
statusDraftList.get(0).mentions.add(mention);
}
}
if (statusReply.spoiler_text != null) {
statusDraftList.get(0).spoiler_text = statusReply.spoiler_text;
}
//StatusDraftList at this point should only have one element
statusList.addAll(statusDraftList);
composeAdapter = new ComposeAdapter(statusList, statusCount, account, accountMention, visibility);
composeAdapter.manageDrafts = this;
LinearLayoutManager mLayoutManager = new LinearLayoutManager(ComposeActivity.this);
binding.recyclerView.setLayoutManager(mLayoutManager);
binding.recyclerView.setAdapter(composeAdapter);
statusesVM.getContext(currentInstance, BaseMainActivity.currentToken, statusReply.id)
.observe(ComposeActivity.this, this::initializeContextView);
} else {
//Compose without replying
statusList.addAll(statusDraftList);
@ -348,19 +343,20 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
if (statusMention != null) {
composeAdapter.loadMentions(statusMention);
}
}
MastodonHelper.loadPPMastodon(binding.profilePicture, account.mastodon_account);
LocalBroadcastManager.getInstance(this)
.registerReceiver(imageReceiver,
new IntentFilter(Helper.INTENT_SEND_MODIFIED_IMAGE));
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
storeDraft(false);
}
}, 0, 10000);
if (timer != null) {
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
storeDraft(false);
}
}, 0, 10000);
}
if (sharedUriList != null && sharedUriList.size() > 0) {
@ -395,6 +391,9 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
@Override
protected void onDestroy() {
super.onDestroy();
if (timer != null) {
timer.cancel();
}
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(imageReceiver);
}
@ -579,6 +578,46 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
} else {
Toasty.info(ComposeActivity.this, getString(R.string.toot_error_no_content), Toasty.LENGTH_SHORT).show();
}
} else if (item.getItemId() == R.id.action_language) {
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ComposeActivity.this);
List<Languages.Language> languages = Languages.get(ComposeActivity.this);
String[] codesArr = new String[0];
String[] languagesArr = new String[0];
String currentCode = sharedpreferences.getString(getString(R.string.SET_COMPOSE_LANGUAGE) + account.user_id + account.instance, null);
int selection = 0;
if (languages != null) {
codesArr = new String[languages.size()];
languagesArr = new String[languages.size()];
int i = 0;
for (Languages.Language language : languages) {
codesArr[i] = language.code;
languagesArr[i] = language.language;
if (currentCode != null && currentCode.equalsIgnoreCase(language.code)) {
selection = i;
}
i++;
}
}
SharedPreferences.Editor editor = sharedpreferences.edit();
AlertDialog.Builder builder = new AlertDialog.Builder(ComposeActivity.this, Helper.dialogStyle());
builder.setTitle(getString(R.string.message_language));
builder.setSingleChoiceItems(languagesArr, selection, null);
String[] finalCodesArr = codesArr;
builder.setPositiveButton(R.string.validate, (dialog, which) -> {
int selectedPosition = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
editor.putString(getString(R.string.SET_COMPOSE_LANGUAGE) + account.user_id + account.instance, finalCodesArr[selectedPosition]);
editor.apply();
dialog.dismiss();
});
builder.setNegativeButton(R.string.reset, (dialog, which) -> {
editor.putString(getString(R.string.SET_COMPOSE_LANGUAGE) + account.user_id + account.instance, null);
editor.apply();
dialog.dismiss();
});
builder.create().show();
}
return true;
}
@ -667,6 +706,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
} else {
statusReplies.add(status);
}
}
if (statusDraft == null) {
statusDraft = new StatusDraft(ComposeActivity.this);
@ -677,10 +717,12 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
}
}
if (statusReplies.size() > 0) {
statusDraft.statusReplyList = statusReplies;
statusDraft.statusReplyList = new ArrayList<>();
statusDraft.statusReplyList.addAll(statusReplies);
}
if (statusDrafts.size() > 0) {
statusDraft.statusDraftList = statusDrafts;
statusDraft.statusDraftList = new ArrayList<>();
statusDraft.statusDraftList.addAll(statusDrafts);
}
if (statusDraft.instance == null) {
statusDraft.instance = account.instance;
@ -750,6 +792,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
intent.putExtra(Helper.ARG_STATUS_DRAFT, statusDraft);
intent.putExtra(Helper.ARG_INSTANCE, instance);
intent.putExtra(Helper.ARG_TOKEN, token);
intent.putExtra(Helper.ARG_USER_ID, account.user_id);
intent.putExtra(Helper.ARG_SCHEDULED_DATE, scheduledDate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent);
@ -757,7 +800,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
startService(intent);
}
} else {
new ThreadMessageService(ComposeActivity.this, instance, token, statusDraft, scheduledDate);
new ThreadMessageService(ComposeActivity.this, instance, account.user_id, token, statusDraft, scheduledDate);
}
finish();
}

View file

@ -43,7 +43,6 @@ import app.fedilab.android.databinding.ActivityConversationBinding;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonContext;
import app.fedilab.android.viewmodel.mastodon.StatusesVM;
@ -82,21 +81,14 @@ public class ContextActivity extends BaseActivity {
focusedStatus = null; // or other values
if (b != null)
focusedStatus = (Status) b.getSerializable(Helper.ARG_STATUS);
if (focusedStatus == null && currentAccount == null || currentAccount.mastodon_account == null) {
if (focusedStatus == null || currentAccount == null || currentAccount.mastodon_account == null) {
finish();
return;
}
MastodonHelper.loadPPMastodon(binding.profilePicture, currentAccount.mastodon_account);
Bundle bundle = new Bundle();
new Thread(() -> {
focusedStatus = SpannableHelper.convertStatus(getApplication().getApplicationContext(), focusedStatus);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
bundle.putSerializable(Helper.ARG_STATUS, focusedStatus);
currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, new FragmentMastodonContext(), bundle, null, null);
};
mainHandler.post(myRunnable);
}).start();
bundle.putSerializable(Helper.ARG_STATUS, focusedStatus);
currentFragment = Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_content_main, new FragmentMastodonContext(), bundle, null, null);
StatusesVM timelinesVM = new ViewModelProvider(ContextActivity.this).get(StatusesVM.class);
timelinesVM.getStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, focusedStatus.id).observe(ContextActivity.this, status -> {
if (status != null) {
@ -168,10 +160,4 @@ public class ContextActivity extends BaseActivity {
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
binding = null;
currentFragment = null;
}
}

View file

@ -205,12 +205,6 @@ public class DraftActivity extends BaseActivity implements StatusDraftAdapter.Dr
}
}
@Override
public void onDestroy() {
super.onDestroy();
binding.lvStatus.setAdapter(null);
binding = null;
}
@Override
public void onAllDeleted() {

View file

@ -294,7 +294,6 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface {
@Override
public void onDestroy() {
binding = null;
unregisterReceiver(onDownloadComplete);
super.onDestroy();
}

View file

@ -27,11 +27,7 @@ import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
@ -64,6 +60,7 @@ import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.tabs.TabLayout;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -148,8 +145,6 @@ public class ProfileActivity extends BaseActivity {
account_id = b.getString(Helper.ARG_USER_ID, null);
mention_str = b.getString(Helper.ARG_MENTION, null);
}
postponeEnterTransition();
//Remove title
@ -165,14 +160,7 @@ public class ProfileActivity extends BaseActivity {
binding.toolbar.setPopupTheme(Helper.popupStyle());
accountsVM = new ViewModelProvider(ProfileActivity.this).get(AccountsVM.class);
if (account != null) {
new Thread(() -> {
account = SpannableHelper.convertAccount(ProfileActivity.this, account);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> initializeView(account);
mainHandler.post(myRunnable);
}).start();
initializeView(account);
} else if (account_id != null) {
accountsVM.getAccount(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account_id).observe(ProfileActivity.this, fetchedAccount -> {
account = fetchedAccount;
@ -359,11 +347,11 @@ public class ProfileActivity extends BaseActivity {
binding.fieldsContainer.setAdapter(fieldAdapter);
binding.fieldsContainer.setLayoutManager(new LinearLayoutManager(ProfileActivity.this));
}
if (account.span_display_name == null && account.display_name == null) {
binding.accountDn.setText(account.username);
} else {
binding.accountDn.setText(account.span_display_name != null ? account.span_display_name : account.display_name, TextView.BufferType.SPANNABLE);
}
binding.accountDn.setText(
account.getSpanDisplayName(ProfileActivity.this,
new WeakReference<>(binding.accountDn)),
TextView.BufferType.SPANNABLE);
binding.accountUn.setText(String.format("@%s", account.acct));
binding.accountUn.setOnLongClickListener(v -> {
@ -377,12 +365,10 @@ public class ProfileActivity extends BaseActivity {
clipboard.setPrimaryClip(clip);
return false;
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
binding.accountNote.setText(account.span_note != null ? account.span_note : new SpannableString(Html.fromHtml(account.note, Html.FROM_HTML_MODE_COMPACT)), TextView.BufferType.SPANNABLE);
else
binding.accountNote.setText(account.span_note != null ? account.span_note : new SpannableString(Html.fromHtml(account.note)), TextView.BufferType.SPANNABLE);
binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this,
new WeakReference<>(binding.accountNote)),
TextView.BufferType.SPANNABLE);
binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance());
MastodonHelper.loadPPMastodon(binding.accountPp, account);

View file

@ -53,6 +53,7 @@ public class SearchResultTabActivity extends BaseActivity {
private String search;
private ActivitySearchResultTabsBinding binding;
private TabLayout.Tab initial;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -77,7 +78,8 @@ public class SearchResultTabActivity extends BaseActivity {
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
}
setTitle(search);
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.tags)));
initial = binding.searchTabLayout.newTab();
binding.searchTabLayout.addTab(initial.setText(getString(R.string.tags)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.accounts)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.toots)));
binding.searchTabLayout.addTab(binding.searchTabLayout.newTab().setText(getString(R.string.action_cache)));
@ -125,7 +127,6 @@ public class SearchResultTabActivity extends BaseActivity {
@Override
public boolean onQueryTextSubmit(String query) {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
assert imm != null;
imm.hideSoftInputFromWindow(binding.searchTabLayout.getWindowToken(), 0);
query = query.replaceAll("^#+", "");
search = query.trim();
@ -134,6 +135,7 @@ public class SearchResultTabActivity extends BaseActivity {
searchView.clearFocus();
setTitle(search);
searchView.setIconified(true);
binding.searchTabLayout.selectTab(initial);
return false;
}

View file

@ -16,6 +16,7 @@ package app.fedilab.android.activities;
import static app.fedilab.android.BaseMainActivity.currentAccount;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.MenuItem;
@ -62,6 +63,7 @@ public class SettingsActivity extends BaseActivity {
}
canGoBack = false;
binding.setAccount.setOnClickListener(v -> displaySettings(SettingsEnum.ACCOUNT));
binding.setTimelines.setOnClickListener(v -> displaySettings(SettingsEnum.TIMELINES));
binding.setNotifications.setOnClickListener(v -> displaySettings(SettingsEnum.NOTIFICATIONS));
binding.setInterface.setOnClickListener(v -> displaySettings(SettingsEnum.INTERFACE));
@ -79,61 +81,66 @@ public class SettingsActivity extends BaseActivity {
public void displaySettings(SettingsEnum settingsEnum) {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
String category = "";
switch (settingsEnum) {
case TIMELINES:
FragmentTimelinesSettings fragmentTimelinesSettings = new FragmentTimelinesSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentTimelinesSettings);
currentFragment = fragmentTimelinesSettings;
category = getString(R.string.settings_category_label_timelines);
break;
case NOTIFICATIONS:
FragmentNotificationsSettings fragmentNotificationsSettings = new FragmentNotificationsSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentNotificationsSettings);
currentFragment = fragmentNotificationsSettings;
category = getString(R.string.notifications);
break;
case INTERFACE:
FragmentInterfaceSettings fragmentInterfaceSettings = new FragmentInterfaceSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentInterfaceSettings);
currentFragment = fragmentInterfaceSettings;
category = getString(R.string.settings_category_label_interface);
break;
case COMPOSE:
FragmentComposeSettings fragmentComposeSettings = new FragmentComposeSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentComposeSettings);
currentFragment = fragmentComposeSettings;
category = getString(R.string.compose);
break;
case PRIVACY:
FragmentPrivacySettings fragmentPrivacySettings = new FragmentPrivacySettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentPrivacySettings);
currentFragment = fragmentPrivacySettings;
category = getString(R.string.action_privacy);
break;
case THEMING:
FragmentThemingSettings fragmentThemingSettings = new FragmentThemingSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentThemingSettings);
currentFragment = fragmentThemingSettings;
category = getString(R.string.theming);
break;
case LANGUAGE:
FragmentLanguageSettings fragmentLanguageSettings = new FragmentLanguageSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentLanguageSettings);
currentFragment = fragmentLanguageSettings;
category = getString(R.string.languages);
break;
if (settingsEnum == SettingsEnum.ACCOUNT) {
Intent intent = new Intent(SettingsActivity.this, EditProfileActivity.class);
startActivity(intent);
} else {
ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
String category = "";
switch (settingsEnum) {
case TIMELINES:
FragmentTimelinesSettings fragmentTimelinesSettings = new FragmentTimelinesSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentTimelinesSettings);
currentFragment = fragmentTimelinesSettings;
category = getString(R.string.settings_category_label_timelines);
break;
case NOTIFICATIONS:
FragmentNotificationsSettings fragmentNotificationsSettings = new FragmentNotificationsSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentNotificationsSettings);
currentFragment = fragmentNotificationsSettings;
category = getString(R.string.notifications);
break;
case INTERFACE:
FragmentInterfaceSettings fragmentInterfaceSettings = new FragmentInterfaceSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentInterfaceSettings);
currentFragment = fragmentInterfaceSettings;
category = getString(R.string.settings_category_label_interface);
break;
case COMPOSE:
FragmentComposeSettings fragmentComposeSettings = new FragmentComposeSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentComposeSettings);
currentFragment = fragmentComposeSettings;
category = getString(R.string.compose);
break;
case PRIVACY:
FragmentPrivacySettings fragmentPrivacySettings = new FragmentPrivacySettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentPrivacySettings);
currentFragment = fragmentPrivacySettings;
category = getString(R.string.action_privacy);
break;
case THEMING:
FragmentThemingSettings fragmentThemingSettings = new FragmentThemingSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentThemingSettings);
currentFragment = fragmentThemingSettings;
category = getString(R.string.theming);
break;
case LANGUAGE:
FragmentLanguageSettings fragmentLanguageSettings = new FragmentLanguageSettings();
fragmentTransaction.replace(R.id.fragment_container, fragmentLanguageSettings);
currentFragment = fragmentLanguageSettings;
category = getString(R.string.languages);
break;
}
String title = String.format(Locale.getDefault(), "%s - %s", getString(R.string.settings), category);
setTitle(title);
canGoBack = true;
fragmentTransaction.commit();
});
}
String title = String.format(Locale.getDefault(), "%s - %s", getString(R.string.settings), category);
setTitle(title);
canGoBack = true;
fragmentTransaction.commit();
});
}
}
@ -156,15 +163,6 @@ public class SettingsActivity extends BaseActivity {
}
@Override
protected void onDestroy() {
super.onDestroy();
if (currentFragment != null) {
currentFragment.onDestroy();
}
binding = null;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@ -177,6 +175,8 @@ public class SettingsActivity extends BaseActivity {
public enum SettingsEnum {
@SerializedName("ACCOUNT")
ACCOUNT("ACCOUNT"),
@SerializedName("TIMELINES")
TIMELINES("TIMELINES"),
@SerializedName("NOTIFICATIONS")

View file

@ -0,0 +1,40 @@
package app.fedilab.android.client.endpoints;
/* 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 retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.Header;
import retrofit2.http.PUT;
import retrofit2.http.Path;
public interface PleromaAPI {
@PUT("pleroma/statuses/{id}/reactions/{name}")
Call<Void> addReaction(
@Header("Authorization") String app_token,
@Path("id") String id,
@Path("name") String name
);
@DELETE("pleroma/statuses/{id}/reactions/{name}")
Call<Void> removeReaction(
@Header("Authorization") String app_token,
@Path("id") String id,
@Path("name") String name
);
}

View file

@ -14,15 +14,20 @@ package app.fedilab.android.client.entities.api;
* 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.content.Context;
import android.text.Spannable;
import android.view.View;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import app.fedilab.android.helper.SpannableHelper;
public class Account implements Serializable {
@SerializedName("id")
@ -74,11 +79,21 @@ public class Account implements Serializable {
@SerializedName("moved")
public Account moved;
//Some extra spannable element - They will be filled automatically when fetching the account
public transient Spannable span_display_name;
public transient Spannable span_note;
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, true, viewWeakReference);
}
public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, note, null, this, null, true, viewWeakReference);
}
public transient RelationShip relationShip;
public static class AccountParams implements Serializable {
@SerializedName("discoverable")
public boolean discoverable;

View file

@ -14,13 +14,18 @@ package app.fedilab.android.client.entities.api;
* 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.content.Context;
import android.text.Spannable;
import android.view.View;
import com.google.gson.annotations.SerializedName;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import app.fedilab.android.helper.SpannableHelper;
public class Announcement {
@SerializedName("id")
public String id;
@ -49,6 +54,9 @@ public class Announcement {
@SerializedName("reactions")
public List<Reaction> reactions;
//Some extra spannable element - They will be filled automatically when fetching the status
public transient Spannable span_content;
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, content, null, null, this, true, viewWeakReference);
}
}

View file

@ -14,13 +14,22 @@ package app.fedilab.android.client.entities.api;
* 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.content.Context;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import androidx.core.content.ContextCompat;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Date;
import app.fedilab.android.R;
import app.fedilab.android.helper.SpannableHelper;
public class Field implements Serializable {
@SerializedName("name")
public String name;
@ -30,7 +39,19 @@ public class Field implements Serializable {
public Date verified_at;
//Some extra spannable element - They will be filled automatically when fetching the account
public transient Spannable value_span;
private transient ForegroundColorSpan value_span;
public synchronized Spannable getValueSpan(Context context, Account account, WeakReference<View> viewWeakReference) {
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, viewWeakReference);
if (value_span != null && spannable != null) {
spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannable;
}
public static class FieldParams implements Serializable {
@SerializedName("name")

View file

@ -1,5 +1,6 @@
package app.fedilab.android.client.entities.api;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
@ -113,6 +114,25 @@ public class Instance implements Serializable {
return mimeTypes;
}
public static String serialize(Instance instance) {
Gson gson = new Gson();
try {
return gson.toJson(instance);
} catch (Exception e) {
return null;
}
}
public static Instance restore(String serialized) {
Gson gson = new Gson();
try {
return gson.fromJson(serialized, Instance.class);
} catch (Exception e) {
return null;
}
}
public static class Configuration implements Serializable {
@SerializedName("statuses")
public StatusesConf statusesConf;

View file

@ -29,6 +29,8 @@ public class Notification {
public String type;
@SerializedName("created_at")
public Date created_at;
@SerializedName("emoji")
public String emoji;
@SerializedName("account")
public Account account;
@SerializedName("status")

View file

@ -0,0 +1,25 @@
package app.fedilab.android.client.entities.api;
/* 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 Pleroma implements Serializable {
@SerializedName("emoji_reactions")
public List<Reaction> emoji_reactions;
}

View file

@ -14,14 +14,19 @@ package app.fedilab.android.client.entities.api;
* 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.content.Context;
import android.text.Spannable;
import android.view.View;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import app.fedilab.android.helper.SpannableHelper;
public class Poll implements Serializable {
@SerializedName("id")
@ -53,7 +58,11 @@ public class Poll implements Serializable {
@SerializedName("votes_count")
public int votes_count;
//Some extra spannable element - They will be filled automatically when fetching the poll
public transient Spannable span_title;
public Spannable getSpanTitle(Context context, Status status, WeakReference<View> viewWeakReference) {
span_title = SpannableHelper.convert(context, title, status, null, null, true, viewWeakReference);
return span_title;
}
}
}

View file

@ -16,7 +16,9 @@ package app.fedilab.android.client.entities.api;
import com.google.gson.annotations.SerializedName;
public class Reaction {
import java.io.Serializable;
public class Reaction implements Serializable {
@SerializedName("name")
public String name;
@SerializedName("count")

View file

@ -14,16 +14,21 @@ package app.fedilab.android.client.entities.api;
* 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.content.Context;
import android.text.Spannable;
import android.view.View;
import androidx.annotation.NonNull;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
import app.fedilab.android.helper.SpannableHelper;
public class Status implements Serializable, Cloneable {
@SerializedName("id")
@ -84,14 +89,12 @@ public class Status implements Serializable, Cloneable {
public Card card;
@SerializedName("poll")
public Poll poll;
@SerializedName("pleroma")
public Pleroma pleroma;
public Attachment art_attachment;
//Some extra spannable element - They will be filled automatically when fetching the status
public transient Spannable span_content;
public transient Spannable span_spoiler_text;
public transient Spannable span_translate;
public boolean isExpended = false;
public boolean isTruncated = true;
public boolean isFetchMore = false;
@ -105,6 +108,24 @@ public class Status implements Serializable, Cloneable {
public transient boolean setCursorToEnd = false;
public transient int cursorPosition = 0;
public transient boolean submitted = false;
//Some extra spannable element - They will be filled automatically when fetching the status
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, content, this, null, null, true, viewWeakReference);
}
public Spannable getSpanContentNitter() {
return SpannableHelper.convertNitter(content);
}
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, spoiler_text, this, null, null, true, viewWeakReference);
}
public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, translationContent, this, null, null, true, viewWeakReference);
}
@NonNull
public Object clone() throws CloneNotSupportedException {

View file

@ -295,7 +295,7 @@ public class Account extends BaseAccount implements Serializable {
}
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, null, null, null, null, null, null);
return cursorToListUser(c);
return cursorToListUserWithOwner(c);
} catch (Exception e) {
return null;
}

View file

@ -0,0 +1,62 @@
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 androidx.appcompat.app.AppCompatActivity;
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class Languages implements Serializable {
@SerializedName("languages")
public List<Language> languages;
public static List<Language> get(AppCompatActivity activity) {
try {
InputStream is = activity.getAssets().open("languages/iso_639_1.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
String json = new String(buffer, StandardCharsets.UTF_8);
Gson gson = new Gson();
try {
return gson.fromJson(json, new TypeToken<List<Language>>() {
}.getType());
} catch (Exception e) {
return null;
}
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
}
public static class Language implements Serializable {
@SerializedName("code")
public String code;
@SerializedName("language")
public String language;
}
}

View file

@ -29,7 +29,6 @@ import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.sqlite.Sqlite;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonNotification;
@ -576,7 +575,6 @@ public class QuickLoad {
}
quickLoad.position = c.getInt(c.getColumnIndexOrThrow(Sqlite.COL_POSITION));
//TimelineHelper.filterStatus(_mContext, quickLoad.statuses, TimelineHelper.FilterTimeLineType.PUBLIC);
quickLoad.statuses = SpannableHelper.convertStatus(_mContext, quickLoad.statuses);
return quickLoad;
}

View file

@ -55,7 +55,7 @@ public class MisskeyNote implements Serializable {
@SerializedName("emojis")
public List<MisskeyEmoji> emojis;
public static Status convert(MisskeyNote misskeyNote) {
public static Status convert(MisskeyNote misskeyNote, String instance) {
Status status = new Status();
status.id = misskeyNote.id;
status.in_reply_to_id = misskeyNote.replyId;
@ -64,7 +64,10 @@ public class MisskeyNote implements Serializable {
status.spoiler_text = misskeyNote.cw;
status.visibility = misskeyNote.visibility;
status.created_at = misskeyNote.createdAt;
status.uri = misskeyNote.uri;
if (misskeyNote.url == null) {
misskeyNote.url = "https://" + instance + "/notes/" + misskeyNote.id;
}
status.uri = misskeyNote.uri != null ? misskeyNote.uri : misskeyNote.url;
status.url = misskeyNote.url;
Account account = new Account();

View file

@ -105,8 +105,8 @@ public class Nitter implements Serializable {
}
}
Nitter nitterAccount = accounts.get(feedItem.creator);
app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account();
if (nitterAccount != null) {
app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account();
String[] names = nitterAccount.image.title.split("/");
account.id = feedItem.guid;
account.acct = names[1].replace("@", "");
@ -116,6 +116,15 @@ public class Nitter implements Serializable {
account.avatar_static = nitterAccount.image.url;
account.url = nitterAccount.image.link;
status.account = account;
} else {
account.id = feedItem.guid;
account.acct = feedItem.creator.replace("@", "");
account.username = feedItem.creator.replace("@", "");
account.display_name = feedItem.creator.replace("@", "");
account.avatar = "";
account.avatar_static = "";
account.url = feedItem.link;
status.account = account;
}
if (feedItem.description != null) {

View file

@ -149,7 +149,7 @@ public class CrossActionHelper {
}
});
} else if (targetedStatus != null) {
searchVM.search(ownerAccount.instance, ownerAccount.token, targetedStatus.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(ownerAccount.instance, ownerAccount.token, targetedStatus.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status status = results.statuses.get(0);
@ -278,8 +278,6 @@ public class CrossActionHelper {
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
} else {
results.statuses = SpannableHelper.convertStatus(context, results.statuses);
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();
@ -308,15 +306,15 @@ public class CrossActionHelper {
/**
* Fetch and federate the remote status
*/
public static void fetchRemoteAccount(@NonNull Context context, @NonNull BaseAccount ownerAccount, app.fedilab.android.client.entities.api.Account targetedAccount, Callback callback) {
public static void fetchRemoteAccount(@NonNull Context context, @NonNull BaseAccount ownerAccount, String targetedAcct, Callback callback) {
MastodonSearchService mastodonSearchService = init(context, BaseMainActivity.currentInstance);
String search;
if (targetedAccount.acct.contains("@")) { //Not from same instance
search = targetedAccount.acct;
if (targetedAcct.contains("@")) { //Not from same instance
search = targetedAcct;
} else {
search = targetedAccount.acct + "@" + BaseMainActivity.currentInstance;
search = targetedAcct + "@" + BaseMainActivity.currentInstance;
}
new Thread(() -> {
Call<Results> resultsCall = mastodonSearchService.search(ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1);
@ -329,8 +327,6 @@ public class CrossActionHelper {
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
} else {
results.statuses = SpannableHelper.convertStatus(context, results.statuses);
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();
@ -374,8 +370,6 @@ public class CrossActionHelper {
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
} else {
results.statuses = SpannableHelper.convertStatus(context, results.statuses);
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();

View file

@ -0,0 +1,106 @@
package app.fedilab.android.helper;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.text.style.ReplacementSpan;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference;
import app.fedilab.android.R;
public class CustomEmoji extends ReplacementSpan {
private final float scale;
private final WeakReference<View> viewWeakReference;
private Drawable imageDrawable;
CustomEmoji(WeakReference<View> viewWeakReference) {
Context mContext = viewWeakReference.get().getContext();
this.viewWeakReference = viewWeakReference;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
scale = sharedpreferences.getFloat(mContext.getString(R.string.SET_FONT_SCALE), 1.0f);
}
@Override
public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) {
if (fontMetricsInt != null) {
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
fontMetricsInt.top = (int) fontMetrics.top;
fontMetricsInt.ascent = (int) fontMetrics.ascent;
fontMetricsInt.descent = (int) fontMetrics.descent;
fontMetricsInt.bottom = (int) fontMetrics.bottom;
}
return (int) (paint.getTextSize() * scale);
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence charSequence, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
if (imageDrawable != null) {
canvas.save();
int emojiSize = (int) (paint.getTextSize() * scale);
Drawable drawable = imageDrawable;
drawable.setBounds(0, 0, emojiSize, emojiSize);
int transY = bottom - drawable.getBounds().bottom;
transY -= paint.getFontMetrics().descent / 2;
canvas.translate(x, (float) transY);
drawable.draw(canvas);
canvas.restore();
}
}
public Target<Drawable> getTarget(boolean animate) {
return new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
View view = viewWeakReference.get();
if (animate && resource instanceof Animatable) {
Drawable.Callback callback = resource.getCallback();
resource.setCallback(new Drawable.Callback() {
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (callback != null) {
callback.invalidateDrawable(drawable);
}
view.invalidate();
}
@Override
public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) {
if (callback != null) {
callback.scheduleDrawable(drawable, runnable, l);
}
}
@Override
public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) {
if (callback != null) {
callback.unscheduleDrawable(drawable, runnable);
}
}
});
((Animatable) resource).start();
}
imageDrawable = resource;
view.invalidate();
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
};
}
}

View file

@ -87,8 +87,10 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.request.RequestOptions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -119,7 +121,7 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -352,9 +354,9 @@ public class Helper {
public static int counter = 1;
static {
Map<PatternType, Pattern> aMap = new HashMap<>();
aMap.put(PatternType.MENTION, mentionPattern);
LinkedHashMap<PatternType, Pattern> aMap = new LinkedHashMap<>();
aMap.put(PatternType.MENTION_LONG, mentionLongPattern);
aMap.put(PatternType.MENTION, mentionPattern);
aMap.put(PatternType.TAG, hashtagPattern);
aMap.put(PatternType.GROUP, groupPattern);
patternHashMap = Collections.unmodifiableMap(aMap);
@ -1043,6 +1045,7 @@ public class Helper {
}
/**
* Load a profile picture for the account
*
@ -1050,25 +1053,39 @@ public class Helper {
* @param account - {@link Account}
*/
public static void loadPP(ImageView view, BaseAccount account) {
loadPP(view, account, false);
}
/**
* Load a profile picture for the account
*
* @param view ImageView - the view where the image will be loaded
* @param account - {@link Account}
*/
public static void loadPP(ImageView view, BaseAccount account, boolean crop) {
Context context = view.getContext();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
String targetedUrl = disableGif ? account.mastodon_account.avatar_static : account.mastodon_account.avatar;
if (targetedUrl != null) {
if (disableGif || (!targetedUrl.endsWith(".gif"))) {
Glide.with(view.getContext())
RequestBuilder<Drawable> requestBuilder = Glide.with(view.getContext())
.asDrawable()
.load(targetedUrl)
.thumbnail(0.1f)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
.thumbnail(0.1f);
if (crop) {
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)));
}
requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))).into(view);
} else {
Glide.with(view.getContext())
RequestBuilder<GifDrawable> requestBuilder = Glide.with(view.getContext())
.asGif()
.load(targetedUrl)
.thumbnail(0.1f)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
.thumbnail(0.1f);
if (crop) {
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)));
}
requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))).into(view);
}
} else {
Glide.with(view.getContext())
@ -1722,4 +1739,13 @@ public class Helper {
}).start();
}
public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
for (Map.Entry<T, E> entry : map.entrySet()) {
if (Objects.equals(value, entry.getValue())) {
return entry.getKey();
}
}
return null;
}
}

View file

@ -0,0 +1,12 @@
package app.fedilab.android.helper;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public final class MyAppGlideModule extends AppGlideModule {
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}

View file

@ -115,11 +115,6 @@ public class NotificationsHelper {
if (notifications.notifications.size() > 0) {
since_ids.put(slug, notifications.notifications.get(0).id);
}
for (Notification notification : notifications.notifications) {
if (notification != null && notification.status != null) {
notification.status = SpannableHelper.convertStatus(context.getApplicationContext(), notification.status);
}
}
}
notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers());
}

View file

@ -107,6 +107,18 @@ public class TimelineHelper {
Matcher m = p.matcher(content);
if (m.find()) {
statusesToRemove.add(status);
continue;
}
if (status.spoiler_text != null) {
String spoilerText;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
spoilerText = Html.fromHtml(status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
else
spoilerText = Html.fromHtml(status.spoiler_text).toString();
Matcher ms = p.matcher(spoilerText);
if (ms.find()) {
statusesToRemove.add(status);
}
}
}
} else {
@ -118,6 +130,18 @@ public class TimelineHelper {
content = Html.fromHtml(status.content).toString();
if (content.contains(filter.phrase)) {
statusesToRemove.add(status);
continue;
}
if (status.spoiler_text != null) {
String spoilerText;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
spoilerText = Html.fromHtml(status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
else
spoilerText = Html.fromHtml(status.spoiler_text).toString();
if (spoilerText.contains(filter.phrase)) {
statusesToRemove.add(status);
}
}
}
}

View file

@ -75,6 +75,7 @@ public class NotificationsWorker extends Worker {
String instance = getInputData().getString(Helper.ARG_INSTANCE);
String token = getInputData().getString(Helper.ARG_TOKEN);
String statusDraftId = getInputData().getString(Helper.ARG_STATUS_DRAFT_ID);
String userId = getInputData().getString(Helper.ARG_USER_ID);
StatusDraft statusDraft;
try {
statusDraft = new StatusDraft(getApplicationContext()).geStatusDraft(statusDraftId);
@ -82,6 +83,7 @@ public class NotificationsWorker extends Worker {
intent.putExtra(Helper.ARG_STATUS_DRAFT, statusDraft);
intent.putExtra(Helper.ARG_INSTANCE, instance);
intent.putExtra(Helper.ARG_TOKEN, token);
intent.putExtra(Helper.ARG_USER_ID, userId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getApplicationContext().startForegroundService(intent);
} else {

View file

@ -74,6 +74,7 @@ public class ScheduleThreadWorker extends Worker {
Data outputData;
String instance = getInputData().getString(Helper.ARG_INSTANCE);
String token = getInputData().getString(Helper.ARG_TOKEN);
String userId = getInputData().getString(Helper.ARG_USER_ID);
String statusDraftId = getInputData().getString(Helper.ARG_STATUS_DRAFT_ID);
StatusDraft statusDraft;
try {
@ -82,6 +83,7 @@ public class ScheduleThreadWorker extends Worker {
intent.putExtra(Helper.ARG_STATUS_DRAFT, statusDraft);
intent.putExtra(Helper.ARG_INSTANCE, instance);
intent.putExtra(Helper.ARG_TOKEN, token);
intent.putExtra(Helper.ARG_USER_ID, userId);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
getApplicationContext().startForegroundService(intent);
} else {

View file

@ -202,9 +202,10 @@ public class PostMessageService extends IntentService {
if (error) {
return;
}
String language = sharedPreferences.getString(context.getString(R.string.SET_COMPOSE_LANGUAGE) + dataPost.userId + dataPost.instance, null);
if (dataPost.scheduledDate == null) {
statusCall = mastodonStatusesService.createStatus(null, dataPost.token, statuses.get(i).text, attachmentIds, poll_options, poll_expire_in,
poll_multiple, poll_hide_totals, in_reply_to_status, statuses.get(i).sensitive, statuses.get(i).spoiler_text, statuses.get(i).visibility.toLowerCase(), statuses.get(i).language);
poll_multiple, poll_hide_totals, in_reply_to_status, statuses.get(i).sensitive, statuses.get(i).spoiler_text, statuses.get(i).visibility.toLowerCase(), language);
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
@ -339,11 +340,13 @@ public class PostMessageService extends IntentService {
StatusDraft statusDraft = null;
String token = null, instance = null;
String scheduledDate = null;
String userId = null;
if (intent != null && intent.getExtras() != null) {
Bundle b = intent.getExtras();
statusDraft = (StatusDraft) b.getSerializable(Helper.ARG_STATUS_DRAFT);
token = b.getString(Helper.ARG_TOKEN);
instance = b.getString(Helper.ARG_INSTANCE);
userId = b.getString(Helper.ARG_USER_ID);
scheduledDate = b.getString(Helper.ARG_SCHEDULED_DATE);
}
//Should not be null, but a simple security
@ -356,6 +359,7 @@ public class PostMessageService extends IntentService {
DataPost dataPost = new DataPost();
dataPost.instance = instance;
dataPost.token = token;
dataPost.userId = userId;
dataPost.statusDraft = statusDraft;
dataPost.scheduledDate = scheduledDate;
dataPost.notificationBuilder = notificationBuilder;
@ -367,6 +371,7 @@ public class PostMessageService extends IntentService {
static class DataPost {
String instance;
String token;
String userId;
StatusDraft statusDraft;
int messageToSend;
int messageSent;

View file

@ -22,9 +22,10 @@ import app.fedilab.android.client.entities.app.StatusDraft;
public class ThreadMessageService {
public ThreadMessageService(Context context, String instance, String token, StatusDraft statusDraft, String scheduledDate) {
public ThreadMessageService(Context context, String instance, String userId, String token, StatusDraft statusDraft, String scheduledDate) {
PostMessageService.DataPost dataPost = new PostMessageService.DataPost();
dataPost.instance = instance;
dataPost.userId = userId;
dataPost.token = token;
dataPost.scheduledDate = scheduledDate;
dataPost.statusDraft = statusDraft;

View file

@ -37,6 +37,7 @@ import androidx.lifecycle.ViewModelStoreOwner;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference;
import java.util.Date;
import java.util.List;
@ -224,9 +225,15 @@ public class AccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
});
}
accountViewHolder.binding.displayName.setText(account.span_display_name, TextView.BufferType.SPANNABLE);
accountViewHolder.binding.displayName.setText(
account.getSpanDisplayName(context,
new WeakReference<>(accountViewHolder.binding.displayName)),
TextView.BufferType.SPANNABLE);
accountViewHolder.binding.username.setText(String.format("@%s", account.acct));
accountViewHolder.binding.bio.setText(account.span_note, TextView.BufferType.SPANNABLE);
accountViewHolder.binding.bio.setText(
account.getSpanNote(context,
new WeakReference<>(accountViewHolder.binding.bio)),
TextView.BufferType.SPANNABLE);
}
public int getCount() {

View file

@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@ -78,7 +79,10 @@ public class AccountListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
account = getItem(position);
AccountListViewHolder holder = (AccountListViewHolder) viewHolder;
MastodonHelper.loadPPMastodon(holder.binding.avatar, account);
holder.binding.displayName.setText(account.span_display_name, TextView.BufferType.SPANNABLE);
holder.binding.displayName.setText(
account.getSpanDisplayName(context,
new WeakReference<>(holder.binding.displayName)),
TextView.BufferType.SPANNABLE);
holder.binding.username.setText(String.format("@%s", account.acct));
if (searchList != null) {

View file

@ -37,6 +37,7 @@ import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.EmojiPopup;
import com.vanniktech.emoji.one.EmojiOneProvider;
import java.lang.ref.WeakReference;
import java.util.List;
import app.fedilab.android.BaseMainActivity;
@ -75,19 +76,23 @@ public class AnnouncementAdapter extends RecyclerView.Adapter<AnnouncementAdapte
return new AnnouncementHolder(itemBinding);
}
@Override
public void onBindViewHolder(@NonNull AnnouncementHolder holder, int position) {
Announcement announcement = announcements.get(position);
if (announcement.reactions != null && announcement.reactions.size() > 0) {
ReactionAdapter reactionAdapter = new ReactionAdapter(announcement.id, announcement.reactions);
holder.binding.reactionsView.setAdapter(reactionAdapter);
holder.binding.layoutReactions.reactionsView.setAdapter(reactionAdapter);
LinearLayoutManager layoutManager
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
holder.binding.reactionsView.setLayoutManager(layoutManager);
holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager);
} else {
holder.binding.reactionsView.setAdapter(null);
holder.binding.layoutReactions.reactionsView.setAdapter(null);
}
holder.binding.content.setText(announcement.span_content, TextView.BufferType.SPANNABLE);
holder.binding.content.setText(
announcement.getSpanContent(context,
new WeakReference<>(holder.binding.content)),
TextView.BufferType.SPANNABLE);
if (announcement.starts_at != null) {
String dateIni;
String dateEnd;
@ -104,11 +109,11 @@ public class AnnouncementAdapter extends RecyclerView.Adapter<AnnouncementAdapte
} else {
holder.binding.dates.setVisibility(View.GONE);
}
holder.binding.statusEmoji.setOnClickListener(v -> {
holder.binding.layoutReactions.statusEmoji.setOnClickListener(v -> {
EmojiManager.install(new EmojiOneProvider());
final EmojiPopup emojiPopup = EmojiPopup.Builder.fromRootView(holder.binding.statusEmoji).setOnEmojiPopupDismissListener(() -> {
final EmojiPopup emojiPopup = EmojiPopup.Builder.fromRootView(holder.binding.layoutReactions.statusEmoji).setOnEmojiPopupDismissListener(() -> {
InputMethodManager imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(holder.binding.statusEmoji.getWindowToken(), 0);
imm.hideSoftInputFromWindow(holder.binding.layoutReactions.statusEmoji.getWindowToken(), 0);
}).setOnEmojiClickListener((emoji, imageView) -> {
String emojiStr = imageView.getUnicode();
boolean alreadyAdded = false;
@ -138,10 +143,10 @@ public class AnnouncementAdapter extends RecyclerView.Adapter<AnnouncementAdapte
announcementsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, announcement.id, emojiStr);
}
})
.build(holder.binding.fakeEdittext);
.build(holder.binding.layoutReactions.fakeEdittext);
emojiPopup.toggle();
});
holder.binding.statusAddCustomEmoji.setOnClickListener(v -> {
holder.binding.layoutReactions.statusAddCustomEmoji.setOnClickListener(v -> {
final AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle());
int paddingPixel = 15;
float density = context.getResources().getDisplayMetrics().density;

View file

@ -73,6 +73,7 @@ import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import java.io.File;
import java.lang.ref.WeakReference;
import java.text.Normalizer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@ -455,13 +456,18 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
List<Attachment> attachmentList = statusList.get(position).media_attachments;
if (attachmentList != null && attachmentList.size() > 0) {
holder.binding.sensitiveMedia.setVisibility(View.VISIBLE);
if (currentAccount.mastodon_account.source != null) {
holder.binding.sensitiveMedia.setChecked(currentAccount.mastodon_account.source.sensitive);
statusList.get(position).sensitive = currentAccount.mastodon_account.source.sensitive;
} else {
statusList.get(position).sensitive = false;
if (!statusList.get(position).sensitive) {
if (currentAccount.mastodon_account.source != null) {
holder.binding.sensitiveMedia.setChecked(currentAccount.mastodon_account.source.sensitive);
statusList.get(position).sensitive = currentAccount.mastodon_account.source.sensitive;
} else {
statusList.get(position).sensitive = false;
}
}
holder.binding.sensitiveMedia.setOnCheckedChangeListener((buttonView, isChecked) -> statusList.get(position).sensitive = isChecked);
holder.binding.sensitiveMedia.setOnCheckedChangeListener((buttonView, isChecked) -> {
statusList.get(position).sensitive = isChecked;
});
int mediaPosition = 0;
for (Attachment attachment : attachmentList) {
ComposeAttachmentItemBinding composeAttachmentItemBinding = ComposeAttachmentItemBinding.inflate(LayoutInflater.from(context), holder.binding.attachmentsList, false);
@ -664,6 +670,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
return statusList.size();
}
private List<Emoji> emojisList = new ArrayList<>();
/**
* Initialize text watcher for content writing
* It will allow to complete autocomplete edit text while starting words with @, #, : etc.
@ -672,7 +679,6 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
* @return {@link TextWatcher}
*/
public TextWatcher initializeTextWatcher(ComposeAdapter.ComposeViewHolder holder) {
final List<Emoji>[] emojis = new List[]{null};
String pattern = "(.|\\s)*(@[\\w_-]+@[a-z0-9.\\-]+|@[\\w_-]+)";
final Pattern mentionPattern = Pattern.compile(pattern);
@ -958,13 +964,13 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
new Thread(() -> {
List<Emoji> emojisToDisplay = new ArrayList<>();
try {
if (emojis[0] == null) {
emojis[0] = new EmojiInstance(context).getEmojiList(BaseMainActivity.currentInstance);
if (emojisList == null || emojisList.size() == 0) {
emojisList = new EmojiInstance(context).getEmojiList(BaseMainActivity.currentInstance);
}
if (emojis[0] == null) {
if (emojis == null) {
return;
}
for (Emoji emoji : emojis[0]) {
for (Emoji emoji : emojisList) {
if (shortcode != null && emoji.shortcode.contains(shortcode)) {
emojisToDisplay.add(emoji);
if (emojisToDisplay.size() >= 10) {
@ -983,7 +989,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
if (searchA.length > 0) {
final String search = searchA[searchA.length - 1];
holder.binding.content.setOnItemClickListener((parent, view, position, id) -> {
String shortcodeSelected = emojis[0].get(position).shortcode;
String shortcodeSelected = emojisToDisplay.get(position).shortcode;
String deltaSearch = "";
int searchLength = searchDeep;
if (currentCursorPosition < searchDeep) { //Less than 15 characters are written before the cursor position
@ -1032,21 +1038,40 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
@SuppressLint("ClickableViewAccessibility")
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
int theme_statuses_color = -1;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (sharedpreferences.getBoolean("use_custom_theme", false)) {
theme_statuses_color = sharedpreferences.getInt("theme_statuses_color", -1);
}
if (getItemViewType(position) == TYPE_NORMAL) {
Status status = statusList.get(position);
StatusSimpleViewHolder holder = (StatusSimpleViewHolder) viewHolder;
holder.binding.statusContent.setText(status.span_content, TextView.BufferType.SPANNABLE);
holder.binding.statusContent.setText(
status.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)),
TextView.BufferType.SPANNABLE);
MastodonHelper.loadPPMastodon(holder.binding.avatar, status.account);
holder.binding.displayName.setText(status.account.span_display_name, TextView.BufferType.SPANNABLE);
holder.binding.displayName.setText(
status.account.getSpanDisplayName(context,
new WeakReference<>(holder.binding.displayName)),
TextView.BufferType.SPANNABLE);
holder.binding.username.setText(String.format("@%s", status.account.acct));
if (status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText(status.span_spoiler_text, TextView.BufferType.SPANNABLE);
holder.binding.spoiler.setText(
status.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)),
TextView.BufferType.SPANNABLE);
} else {
holder.binding.spoiler.setVisibility(View.GONE);
holder.binding.spoiler.setText(null);
}
if (theme_statuses_color != -1) {
holder.binding.cardviewContainer.setBackgroundColor(theme_statuses_color);
} else {
holder.binding.cardviewContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.cyanea_primary_dark_reference));
}
} else if (getItemViewType(position) == TYPE_COMPOSE) {
Status statusDraft = statusList.get(position);
@ -1058,7 +1083,11 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
int newInputTypeSpoiler = holder.binding.contentSpoiler.getInputType() & (holder.binding.contentSpoiler.getInputType() ^ InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
holder.binding.contentSpoiler.setInputType(newInputTypeSpoiler);
if (theme_statuses_color != -1) {
holder.binding.cardviewContainer.setBackgroundColor(theme_statuses_color);
} else {
holder.binding.cardviewContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.cyanea_primary_dark_reference));
}
holder.binding.buttonAttach.setOnClickListener(v -> {
if (instanceInfo.configuration.media_attachments.supported_mime_types != null) {
if (instanceInfo.getMimeTypeAudio().size() == 0) {

View file

@ -36,6 +36,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
@ -137,14 +138,20 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
notifyItemChanged(position);
});
holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText(conversation.last_status.span_spoiler_text, TextView.BufferType.SPANNABLE);
holder.binding.spoiler.setText(
conversation.last_status.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)),
TextView.BufferType.SPANNABLE);
} else {
holder.binding.spoiler.setVisibility(View.GONE);
holder.binding.spoilerExpand.setVisibility(View.GONE);
holder.binding.spoiler.setText(null);
}
//--- MAIN CONTENT ---
holder.binding.statusContent.setText(conversation.last_status.span_content, TextView.BufferType.SPANNABLE);
holder.binding.statusContent.setText(
conversation.last_status.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)),
TextView.BufferType.SPANNABLE);
//--- DATE ---
holder.binding.lastMessageDate.setText(Helper.dateDiff(context, conversation.last_status.created_at));

View file

@ -16,26 +16,16 @@ package app.fedilab.android.ui.drawer;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.github.penfeizhou.animation.apng.APNGDrawable;
import com.github.penfeizhou.animation.apng.decode.APNGParser;
import com.github.penfeizhou.animation.gif.GifDrawable;
import com.github.penfeizhou.animation.gif.decode.GifParser;
import java.io.File;
import java.util.List;
import app.fedilab.android.R;
@ -52,7 +42,7 @@ public class EmojiAdapter extends BaseAdapter {
}
public int getCount() {
return emojiList.size();
return emojiList == null ? 0 : emojiList.size();
}
public Emoji getItem(int position) {
@ -76,28 +66,8 @@ public class EmojiAdapter extends BaseAdapter {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(holder.view.getContext());
boolean disableAnimatedEmoji = sharedpreferences.getBoolean(parent.getContext().getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
Glide.with(holder.binding.imgCustomEmoji.getContext())
.asFile()
.load(!disableAnimatedEmoji ? emoji.url : emoji.static_url)
.into(new CustomTarget<File>() {
@Override
public void onResourceReady(@NonNull File resource, @Nullable Transition<? super File> transition) {
if (APNGParser.isAPNG(resource.getAbsolutePath())) {
APNGDrawable apngDrawable = APNGDrawable.fromFile(resource.getAbsolutePath());
holder.binding.imgCustomEmoji.setImageDrawable(apngDrawable);
} else if (GifParser.isGif(resource.getAbsolutePath())) {
GifDrawable gifDrawable = GifDrawable.fromFile(resource.getAbsolutePath());
holder.binding.imgCustomEmoji.setImageDrawable(gifDrawable);
} else {
Drawable drawable = Drawable.createFromPath(resource.getAbsolutePath());
holder.binding.imgCustomEmoji.setImageDrawable(drawable);
}
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
.into(holder.binding.imgCustomEmoji);
return holder.view;
}

View file

@ -16,9 +16,7 @@ package app.fedilab.android.ui.drawer;
import android.content.Context;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.TextView;
@ -27,9 +25,11 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference;
import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Field;
import app.fedilab.android.databinding.DrawerFieldBinding;
@ -38,6 +38,7 @@ public class FieldAdapter extends RecyclerView.Adapter<FieldAdapter.FieldViewHol
private final List<Field> fields;
private Context context;
private Account account;
public FieldAdapter(List<Field> fields) {
this.fields = fields;
@ -66,9 +67,11 @@ public class FieldAdapter extends RecyclerView.Adapter<FieldAdapter.FieldViewHol
Field field = fields.get(position);
if (field.verified_at != null) {
holder.binding.value.setCompoundDrawablesWithIntrinsicBounds(null, null, ContextCompat.getDrawable(context, R.drawable.ic_baseline_verified_24), null);
field.value_span.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text)), 0, field.value_span.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
holder.binding.value.setText(field.value_span != null ? field.value_span : field.value, TextView.BufferType.SPANNABLE);
holder.binding.value.setText(
field.getValueSpan(context, account,
new WeakReference<>(holder.binding.value)),
TextView.BufferType.SPANNABLE);
holder.binding.value.setMovementMethod(LinkMovementMethod.getInstance());
holder.binding.label.setText(field.name);
}

View file

@ -24,6 +24,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityOptionsCompat;
@ -31,6 +32,7 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Locale;
@ -58,6 +60,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
private final int TYPE_POLL = 5;
private final int TYPE_STATUS = 6;
private final int NOTIFICATION_FETCH_MORE = 7;
private final int TYPE_REACTION = 8;
public FetchMoreCallBack fetchMoreCallBack;
private Context context;
@ -94,6 +97,8 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
return TYPE_POLL;
case "status":
return TYPE_STATUS;
case "pleroma:emoji_reaction":
return TYPE_REACTION;
}
return super.getItemViewType(position);
}
@ -114,13 +119,36 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
}
}
/**
* Will manage the current position of the element in the adapter. Action is async, and position might have changed
*
* @param notificationList List<Notification> - Not null when calling from notification adapter
* @param id String - Current status
* @return int - position in real time
*/
public static int getPositionAsync(List<Notification> notificationList, String id) {
int position = 0;
if (notificationList != null) {
for (Notification notification : notificationList) {
if (notification.status != null && notification.status.id.compareTo(id) == 0) {
break;
}
position++;
}
}
return position;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
Notification notification = notificationList.get(position);
if (getItemViewType(position) == TYPE_FOLLOW || getItemViewType(position) == TYPE_FOLLOW_REQUEST) {
ViewHolderFollow holderFollow = (ViewHolderFollow) viewHolder;
MastodonHelper.loadPPMastodon(holderFollow.binding.avatar, notification.account);
holderFollow.binding.displayName.setText(notification.account.display_name);
holderFollow.binding.displayName.setText(
notification.account.getSpanDisplayName(context,
new WeakReference<>(holderFollow.binding.displayName)),
TextView.BufferType.SPANNABLE);
holderFollow.binding.username.setText(String.format("@%s", notification.account.acct));
if (getItemViewType(position) == TYPE_FOLLOW_REQUEST) {
holderFollow.binding.rejectButton.setVisibility(View.VISIBLE);
@ -172,6 +200,8 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
holderStatus.bindingNotification.status.typeOfNotification.setImageResource(R.drawable.ic_baseline_star_24);
} else if (getItemViewType(position) == TYPE_REBLOG) {
holderStatus.bindingNotification.status.typeOfNotification.setImageResource(R.drawable.ic_baseline_repeat_24);
} else if (getItemViewType(position) == TYPE_REACTION) {
holderStatus.bindingNotification.status.typeOfNotification.setImageResource(R.drawable.ic_baseline_insert_emoticon_24);
} else if (getItemViewType(position) == TYPE_POLL) {
holderStatus.bindingNotification.status.typeOfNotification.setImageResource(R.drawable.ic_baseline_poll_24);
}
@ -180,15 +210,25 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
statusManagement(context, statusesVM, searchVM, holderStatus, this, null, notificationList, notification.status, Timeline.TimeLineEnum.NOTIFICATION, false, true);
holderStatus.bindingNotification.status.dateShort.setText(Helper.dateDiff(context, notification.created_at));
holderStatus.bindingNotification.containerTransparent.setAlpha(.3f);
if (getItemViewType(position) == TYPE_MENTION || getItemViewType(position) == TYPE_STATUS) {
if (getItemViewType(position) == TYPE_MENTION || getItemViewType(position) == TYPE_STATUS || getItemViewType(position) == TYPE_REACTION) {
holderStatus.bindingNotification.status.actionButtons.setVisibility(View.VISIBLE);
String title = "";
if (getItemViewType(position) == TYPE_MENTION) {
title = String.format(Locale.getDefault(), "%s %s", notification.account.display_name, context.getString(R.string.notif_mention));
} else if (getItemViewType(position) == TYPE_STATUS) {
title = String.format(Locale.getDefault(), "%s %s", notification.account.display_name, context.getString(R.string.notif_status));
} else if (getItemViewType(position) == TYPE_REACTION) {
if (notification.emoji == null) {
notification.emoji = "";
}
title = String.format(Locale.getDefault(), "%s reacted with %s", notification.account.username, notification.emoji);
MastodonHelper.loadPPMastodon(holderStatus.bindingNotification.status.avatar, notification.account);
}
holderStatus.bindingNotification.status.displayName.setText(title);
notification.account.display_name = title;
holderStatus.bindingNotification.status.displayName.setText(
notification.account.getSpanDisplayName(context,
new WeakReference<>(holderStatus.bindingNotification.status.displayName)),
TextView.BufferType.SPANNABLE);
holderStatus.bindingNotification.status.username.setText(String.format("@%s", notification.account.acct));
holderStatus.bindingNotification.containerTransparent.setAlpha(.1f);
if (notification.status != null && notification.status.visibility.equalsIgnoreCase("direct")) {
@ -255,13 +295,19 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
// start the new activity
context.startActivity(intent, options.toBundle());
});
holderStatus.bindingNotification.status.displayName.setText(title);
notification.account.display_name = title;
holderStatus.bindingNotification.status.displayName.setText(
notification.account.getSpanDisplayName(context,
new WeakReference<>(holderStatus.bindingNotification.status.displayName)),
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);
}
}
}
public long getItemId(int position) {
return position;
}

View file

@ -15,6 +15,8 @@ package app.fedilab.android.ui.drawer;
* see <http://www.gnu.org/licenses>. */
import static android.content.Context.INPUT_METHOD_SERVICE;
import static app.fedilab.android.BaseMainActivity.emojis;
import static app.fedilab.android.BaseMainActivity.regex_home;
import static app.fedilab.android.BaseMainActivity.regex_local;
import static app.fedilab.android.BaseMainActivity.regex_public;
@ -35,10 +37,7 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
@ -49,7 +48,9 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.CheckBox;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
@ -69,9 +70,11 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions;
@ -80,14 +83,16 @@ import com.github.stom79.mytransl.client.HttpsConnectionException;
import com.github.stom79.mytransl.client.Results;
import com.github.stom79.mytransl.translate.Params;
import com.github.stom79.mytransl.translate.Translate;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.EmojiPopup;
import com.vanniktech.emoji.one.EmojiOneProvider;
import com.varunest.sparkbutton.SparkButton;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -96,6 +101,7 @@ import app.fedilab.android.R;
import app.fedilab.android.activities.ComposeActivity;
import app.fedilab.android.activities.ContextActivity;
import app.fedilab.android.activities.CustomSharingActivity;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.activities.MediaActivity;
import app.fedilab.android.activities.ProfileActivity;
import app.fedilab.android.activities.ReportActivity;
@ -103,7 +109,9 @@ import app.fedilab.android.activities.StatusInfoActivity;
import app.fedilab.android.client.entities.api.Attachment;
import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.api.Poll;
import app.fedilab.android.client.entities.api.Reaction;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.Account;
import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.client.entities.app.StatusDraft;
import app.fedilab.android.client.entities.app.Timeline;
@ -127,6 +135,7 @@ import app.fedilab.android.ui.fragment.timeline.FragmentMastodonContext;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.SearchVM;
import app.fedilab.android.viewmodel.mastodon.StatusesVM;
import app.fedilab.android.viewmodel.pleroma.ActionsVM;
import es.dmoral.toasty.Toasty;
import jp.wasabeef.glide.transformations.BlurTransformation;
@ -253,6 +262,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
statusToDeal.bookmarked = statusReturned.bookmarked;
statusToDeal.reblogs_count = statusReturned.reblogs_count;
statusToDeal.favourites_count = statusReturned.favourites_count;
//Update status in cache if not a remote instance
if (!remote) {
new Thread(() -> {
@ -315,10 +325,120 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
boolean fullAttachement = sharedpreferences.getBoolean(context.getString(R.string.SET_FULL_PREVIEW), false);
boolean displayBookmark = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_BOOKMARK), false);
if (MainActivity.currentAccount != null && MainActivity.currentAccount.api == Account.API.PLEROMA) {
holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE);
if (status.pleroma != null && status.pleroma.emoji_reactions != null && status.pleroma.emoji_reactions.size() > 0) {
ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.pleroma.emoji_reactions);
holder.binding.layoutReactions.reactionsView.setAdapter(reactionAdapter);
LinearLayoutManager layoutManager
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager);
} else {
holder.binding.layoutReactions.reactionsView.setAdapter(null);
}
holder.binding.layoutReactions.statusEmoji.setOnClickListener(v -> {
EmojiManager.install(new EmojiOneProvider());
final EmojiPopup emojiPopup = EmojiPopup.Builder.fromRootView(holder.binding.layoutReactions.statusEmoji).setOnEmojiPopupDismissListener(() -> {
InputMethodManager imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(holder.binding.layoutReactions.statusEmoji.getWindowToken(), 0);
}).setOnEmojiClickListener((emoji, imageView) -> {
String emojiStr = imageView.getUnicode();
boolean alreadyAdded = false;
for (Reaction reaction : status.pleroma.emoji_reactions) {
if (reaction.name.compareTo(emojiStr) == 0) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
}
adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
break;
}
}
if (!alreadyAdded) {
Reaction reaction = new Reaction();
reaction.me = true;
reaction.count = 1;
reaction.name = emojiStr;
status.pleroma.emoji_reactions.add(0, reaction);
adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
}
ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
if (alreadyAdded) {
actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
}
})
.build(holder.binding.layoutReactions.fakeEdittext);
emojiPopup.toggle();
});
holder.binding.layoutReactions.statusAddCustomEmoji.setOnClickListener(v -> {
final AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle());
int paddingPixel = 15;
float density = context.getResources().getDisplayMetrics().density;
int paddingDp = (int) (paddingPixel * density);
builder.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
builder.setTitle(R.string.insert_emoji);
AlertDialog alertDialogEmoji = null;
if (emojis != null && emojis.size() > 0 && emojis.get(BaseMainActivity.currentInstance) != null) {
GridView gridView = new GridView(context);
gridView.setAdapter(new EmojiAdapter(emojis.get(BaseMainActivity.currentInstance)));
gridView.setNumColumns(5);
AlertDialog finalAlertDialogEmoji = alertDialogEmoji;
gridView.setOnItemClickListener((parent, view, index, id) -> {
String emojiStr = emojis.get(BaseMainActivity.currentInstance).get(index).shortcode;
String url = emojis.get(BaseMainActivity.currentInstance).get(index).url;
String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url;
boolean alreadyAdded = false;
for (Reaction reaction : status.pleroma.emoji_reactions) {
if (reaction.name.compareTo(emojiStr) == 0) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
}
adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
break;
}
}
if (!alreadyAdded) {
Reaction reaction = new Reaction();
reaction.me = true;
reaction.count = 1;
reaction.name = emojiStr;
reaction.url = url;
reaction.static_url = static_url;
status.pleroma.emoji_reactions.add(0, reaction);
adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
}
ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
if (alreadyAdded) {
actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
}
if (finalAlertDialogEmoji != null) {
finalAlertDialogEmoji.dismiss();
}
});
gridView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp);
builder.setView(gridView);
} else {
TextView textView = new TextView(context);
textView.setText(context.getString(R.string.no_emoji));
textView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp);
builder.setView(textView);
}
alertDialogEmoji = builder.show();
});
}
int truncate_toots_size = sharedpreferences.getInt(context.getString(R.string.SET_TRUNCATE_TOOTS_SIZE), 0);
boolean display_video_preview = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_VIDEO_PREVIEWS), true);
boolean isModerator = sharedpreferences.getBoolean(Helper.PREF_IS_MODERATOR, false);
boolean isAdmin = sharedpreferences.getBoolean(Helper.PREF_IS_ADMINISTRATOR, false);
// boolean display_video_preview = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_VIDEO_PREVIEWS), true);
// boolean isModerator = sharedpreferences.getBoolean(Helper.PREF_IS_MODERATOR, false);
// boolean isAdmin = sharedpreferences.getBoolean(Helper.PREF_IS_ADMINISTRATOR, false);
int theme_icons_color = -1;
int theme_statuses_color = -1;
int theme_boost_header_color = -1;
@ -400,6 +520,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
if (theme_statuses_color != -1) {
holder.binding.cardviewContainer.setBackgroundColor(theme_statuses_color);
holder.binding.translationLabel.setBackgroundColor(theme_statuses_color);
} else {
holder.binding.cardviewContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.cyanea_primary_dark_reference));
holder.binding.translationLabel.setBackgroundColor(ContextCompat.getColor(context, R.color.cyanea_primary_dark_reference));
}
if (theme_boost_header_color != -1 && status.reblog != null) {
holder.binding.statusBoosterInfo.setBackgroundColor(theme_boost_header_color);
@ -471,6 +594,35 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
holder.binding.card.setVisibility(View.GONE);
}
if (!canBeFederated) {
holder.binding.actionShareContainer.setVisibility(View.VISIBLE);
holder.binding.actionShare.setOnClickListener(v -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via));
String url;
if (statusToDeal.uri.startsWith("http"))
url = status.uri;
else
url = status.url;
String extra_text;
if (share_details) {
extra_text = statusToDeal.account.acct;
if (extra_text.split("@").length == 1)
extra_text = "@" + extra_text + "@" + BaseMainActivity.currentInstance;
else
extra_text = "@" + extra_text;
extra_text += " \uD83D\uDD17 " + url + "\r\n-\n";
extra_text += statusToDeal.text;
} else {
extra_text = url;
}
sendIntent.putExtra(Intent.EXTRA_TEXT, extra_text);
sendIntent.setType("text/plain");
context.startActivity(Intent.createChooser(sendIntent, context.getString(R.string.share_with)));
});
} else {
holder.binding.actionShareContainer.setVisibility(View.GONE);
}
if (minified || !canBeFederated) {
holder.binding.actionButtons.setVisibility(View.GONE);
} else {
@ -491,14 +643,12 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.actionButtonBookmark.setOnClickListener(v -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = statusList.get(0);
statusesVM.bookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchedStatus.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.BOOKMARK_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.BOOKMARK_ACTION, statusToDeal, _status, true));
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
@ -506,16 +656,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (statusToDeal.bookmarked) {
statusesVM.unBookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNBOOKMARK_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNBOOKMARK_ACTION, statusToDeal, _status, false));
} else {
((SparkButton) v).playAnimation();
statusesVM.bookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.BOOKMARK_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.BOOKMARK_ACTION, statusToDeal, _status, false));
}
}
});
@ -523,7 +668,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.statusUserInfo.setOnClickListener(v -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
@ -550,10 +695,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
context.startActivity(intent, options.toBundle());
}
});
holder.binding.statusBoosterAvatar.setOnClickListener(v -> {
holder.binding.statusBoosterInfo.setOnClickListener(v -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
@ -599,14 +744,12 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
alt_bld.setPositiveButton(R.string.yes, (dialog, id) -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
statusesVM.reblog(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchedStatus.id, null)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, true));
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
@ -614,15 +757,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (statusToDeal.reblogged) {
statusesVM.unReblog(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNREBLOG_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNREBLOG_ACTION, statusToDeal, _status, false));
} else {
((SparkButton) v).playAnimation();
statusesVM.reblog(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, null)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, false));
}
}
dialog.dismiss();
@ -633,14 +772,12 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
statusesVM.reblog(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchedStatus.id, null)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, true));
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
@ -648,15 +785,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (statusToDeal.reblogged) {
statusesVM.unReblog(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNREBLOG_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNREBLOG_ACTION, statusToDeal, _status, false));
} else {
((SparkButton) v).playAnimation();
statusesVM.reblog(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, null)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.REBLOG_ACTION, statusToDeal, _status, false));
}
}
}
@ -681,14 +814,12 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
alt_bld.setPositiveButton(R.string.yes, (dialog, id) -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
statusesVM.favourite(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchedStatus.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, true));
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
@ -696,15 +827,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (status.favourited) {
statusesVM.unFavourite(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNFAVOURITE_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNFAVOURITE_ACTION, statusToDeal, _status, false));
} else {
((SparkButton) v).playAnimation();
statusesVM.favourite(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, false));
}
}
dialog.dismiss();
@ -715,14 +842,12 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
statusesVM.favourite(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, fetchedStatus.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, true));
} else {
Toasty.info(context, context.getString(R.string.toast_error_search), Toasty.LENGTH_SHORT).show();
}
@ -730,15 +855,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (statusToDeal.favourited) {
statusesVM.unFavourite(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNFAVOURITE_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.UNFAVOURITE_ACTION, statusToDeal, _status, false));
} else {
((SparkButton) v).playAnimation();
statusesVM.favourite(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id)
.observe((LifecycleOwner) context, _status -> {
manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, remote);
});
.observe((LifecycleOwner) context, _status -> manageAction(context, adapter, statusList, notificationList, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, statusToDeal, _status, false));
}
}
}
@ -748,11 +869,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//--- ACCOUNT INFO ---
MastodonHelper.loadPPMastodon(holder.binding.avatar, statusToDeal.account);
Spannable span_display_name = statusToDeal.account.span_display_name;
if (span_display_name == null || span_display_name.toString().trim().length() == 0) {
span_display_name = new SpannableString(statusToDeal.account.username);
}
holder.binding.displayName.setText(span_display_name, TextView.BufferType.SPANNABLE);
holder.binding.displayName.setText(
statusToDeal.account.getSpanDisplayName(context,
new WeakReference<>(holder.binding.displayName)),
TextView.BufferType.SPANNABLE);
if (theme_text_header_1_line != -1) {
holder.binding.displayName.setTextColor(theme_text_header_1_line);
}
@ -772,16 +893,27 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
if (statusToDeal.account.bot) {
holder.binding.botIcon.setVisibility(View.VISIBLE);
} else {
holder.binding.botIcon.setVisibility(View.GONE);
}
if (statusToDeal.in_reply_to_id != null) {
if (statusToDeal.in_reply_to_id != null && timelineType != Timeline.TimeLineEnum.UNKNOWN) {
holder.binding.replyIcon.setVisibility(View.VISIBLE);
} else {
holder.binding.replyIcon.setVisibility(View.GONE);
}
int ressource = R.drawable.ic_baseline_public_24;
switch (status.visibility) {
case "unlisted":
ressource = R.drawable.ic_baseline_lock_open_24;
break;
case "private":
ressource = R.drawable.ic_baseline_lock_24;
break;
case "direct":
ressource = R.drawable.ic_baseline_mail_24;
break;
}
if (status.isFocused) {
holder.binding.statusInfo.setVisibility(View.VISIBLE);
holder.binding.reblogsCount.setText(String.valueOf(status.reblogs_count));
@ -789,23 +921,14 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.time.setText(Helper.longDateToString(status.created_at));
holder.binding.time.setVisibility(View.VISIBLE);
holder.binding.dateShort.setVisibility(View.GONE);
int ressource = R.drawable.ic_baseline_public_24;
switch (status.visibility) {
case "unlisted":
ressource = R.drawable.ic_baseline_lock_open_24;
break;
case "private":
ressource = R.drawable.ic_baseline_lock_24;
break;
case "direct":
ressource = R.drawable.ic_baseline_mail_24;
break;
}
holder.binding.visibility.setImageResource(ressource);
holder.binding.dateShort.setVisibility(View.GONE);
holder.binding.visibilitySmall.setVisibility(View.GONE);
} else {
holder.binding.visibilitySmall.setImageResource(ressource);
holder.binding.statusInfo.setVisibility(View.GONE);
holder.binding.dateShort.setVisibility(View.VISIBLE);
holder.binding.visibilitySmall.setVisibility(View.VISIBLE);
holder.binding.dateShort.setText(Helper.dateDiff(context, status.created_at));
holder.binding.time.setVisibility(View.GONE);
Helper.absoluteDateTimeReveal(context, holder.binding.dateShort, status.created_at);
@ -817,7 +940,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
if (expand_cw || expand) {
holder.binding.spoilerExpand.setVisibility(View.VISIBLE);
holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText(statusToDeal.span_spoiler_text, TextView.BufferType.SPANNABLE);
holder.binding.spoiler.setText(
statusToDeal.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)),
TextView.BufferType.SPANNABLE);
statusToDeal.isExpended = true;
statusToDeal.isMediaDisplayed = true;
} else {
@ -828,7 +954,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
});
holder.binding.spoilerExpand.setVisibility(View.VISIBLE);
holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText(statusToDeal.span_spoiler_text, TextView.BufferType.SPANNABLE);
holder.binding.spoiler.setText(
statusToDeal.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)),
TextView.BufferType.SPANNABLE);
}
if (statusToDeal.isExpended) {
holder.binding.spoilerExpand.setText(context.getString(R.string.hide_content));
@ -844,13 +974,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//--- BOOSTER INFO ---
if (status.reblog != null) {
MastodonHelper.loadPPMastodon(holder.binding.statusBoosterAvatar, status.account);
Spannable span_display_name_boost = status.account.span_display_name;
if (span_display_name_boost == null || span_display_name_boost.toString().trim().length() == 0) {
span_display_name_boost = new SpannableString(status.account.username);
}
holder.binding.statusBoosterDisplayName.setText(span_display_name_boost, TextView.BufferType.SPANNABLE);
holder.binding.statusBoosterDisplayName.setText(
status.account.getSpanDisplayName(context,
new WeakReference<>(holder.binding.statusBoosterDisplayName)),
TextView.BufferType.SPANNABLE);
holder.binding.statusBoosterInfo.setVisibility(View.VISIBLE);
holder.binding.boosterDivider.setVisibility(View.VISIBLE);
if (theme_text_header_1_line != -1) {
holder.binding.statusBoosterDisplayName.setTextColor(theme_text_header_1_line);
}
@ -860,7 +990,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
} else {
holder.binding.statusBoosterInfo.setVisibility(View.GONE);
holder.binding.boosterDivider.setVisibility(View.GONE);
}
//--- BOOST VISIBILITY ---
switch (statusToDeal.visibility) {
@ -880,7 +1009,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
break;
}
//--- MAIN CONTENT ---
holder.binding.statusContent.setText(statusToDeal.span_content, TextView.BufferType.SPANNABLE);
holder.binding.statusContent.setText(
statusToDeal.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)),
TextView.BufferType.SPANNABLE);
if (truncate_toots_size > 0) {
holder.binding.statusContent.setMaxLines(truncate_toots_size);
holder.binding.statusContent.setEllipsize(TextUtils.TruncateAt.END);
@ -911,7 +1043,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
if (statusToDeal.translationContent != null) {
holder.binding.containerTrans.setVisibility(View.VISIBLE);
holder.binding.statusContentTranslated.setText(statusToDeal.span_translate, TextView.BufferType.SPANNABLE);
holder.binding.statusContentTranslated.setText(
statusToDeal.getSpanTranslate(context,
new WeakReference<>(holder.binding.statusContentTranslated)),
TextView.BufferType.SPANNABLE);
} else {
holder.binding.containerTrans.setVisibility(View.GONE);
}
@ -925,6 +1060,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.statusContent.setVisibility(View.GONE);
holder.binding.mediaContainer.setVisibility(View.GONE);
}
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
//--- MEDIA ATTACHMENT ---
if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
@ -994,17 +1130,19 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
if (!mediaObfuscated(statusToDeal) || expand_media) {
layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_24);
Glide.with(layoutMediaBinding.media.getContext())
RequestBuilder<Drawable> requestBuilder = Glide.with(layoutMediaBinding.media.getContext())
.load(statusToDeal.media_attachments.get(0).preview_url)
.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)))
.into(layoutMediaBinding.media);
.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)));
if (!fullAttachement) {
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)));
}
requestBuilder.into(layoutMediaBinding.media);
} else {
layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24);
Glide.with(layoutMediaBinding.media.getContext())
.load(statusToDeal.media_attachments.get(0).preview_url)
.apply(new RequestOptions().transform(new BlurTransformation(50, 3)))
// .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)))
.into(layoutMediaBinding.media);
}
layoutMediaBinding.viewHide.setOnClickListener(v -> {
@ -1046,17 +1184,18 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
lp.setMargins(0, 0, (int) Helper.convertDpToPixel(5, context), 0);
if (!mediaObfuscated(statusToDeal) || expand_media) {
layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_24);
Glide.with(layoutMediaBinding.media.getContext())
RequestBuilder<Drawable> requestBuilder = Glide.with(layoutMediaBinding.media.getContext())
.load(attachment.preview_url)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)))
.into(layoutMediaBinding.media);
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context))));
if (!fullAttachement) {
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)));
}
requestBuilder.into(layoutMediaBinding.media);
} else {
layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24);
Glide.with(layoutMediaBinding.media.getContext())
.load(attachment.preview_url)
.apply(new RequestOptions().transform(new BlurTransformation(50, 3)))
.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)))
// .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
.into(layoutMediaBinding.media);
}
@ -1096,7 +1235,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.reblogInfo.setOnClickListener(v -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
@ -1123,7 +1262,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.favouriteInfo.setOnClickListener(v -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
@ -1174,7 +1313,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
pollItemBinding.pollItemPercent.setTextColor(theme_text_color);
pollItemBinding.pollItemText.setTextColor(theme_text_color);
}
pollItemBinding.pollItemText.setText(pollItem.span_title, TextView.BufferType.SPANNABLE);
pollItemBinding.pollItemText.setText(
pollItem.getSpanTitle(context, statusToDeal,
new WeakReference<>(pollItemBinding.pollItemText)),
TextView.BufferType.SPANNABLE);
pollItemBinding.pollItemValue.setProgress((int) value);
if (pollItem.votes_count == greaterValue) {
pollItemBinding.pollItemPercent.setTypeface(null, Typeface.BOLD);
@ -1202,7 +1344,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
for (Poll.PollItem pollOption : statusToDeal.poll.options) {
CheckBox cb = new CheckBox(context);
cb.setButtonTintList(ThemeHelper.getButtonColorStateList(context));
cb.setText(pollOption.span_title, TextView.BufferType.SPANNABLE);
cb.setText(
pollOption.getSpanTitle(context, statusToDeal,
new WeakReference<>(cb)),
TextView.BufferType.SPANNABLE);
holder.binding.poll.multipleChoice.addView(cb);
}
holder.binding.poll.multipleChoice.setVisibility(View.VISIBLE);
@ -1213,7 +1358,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
for (Poll.PollItem pollOption : statusToDeal.poll.options) {
RadioButton rb = new RadioButton(context);
rb.setButtonTintList(ThemeHelper.getButtonColorStateList(context));
rb.setText(pollOption.span_title, TextView.BufferType.SPANNABLE);
rb.setText(
pollOption.getSpanTitle(context, statusToDeal,
new WeakReference<>(rb)),
TextView.BufferType.SPANNABLE);
holder.binding.poll.singleChoiceRadioGroup.addView(rb);
}
holder.binding.poll.singleChoiceRadioGroup.setVisibility(View.VISIBLE);
@ -1256,7 +1405,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//Vote on the poll
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
@ -1322,9 +1471,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
return false;
});
if (!minified && canBeFederated) {
holder.binding.mainContainer.setOnClickListener(v -> {
holder.binding.statusContent.callOnClick();
});
holder.binding.mainContainer.setOnClickListener(v -> holder.binding.statusContent.callOnClick());
holder.binding.statusContent.setOnClickListener(v -> {
if (status.isFocused || v.getTag() == SpannableHelper.CLICKABLE_SPAN) {
if (v.getTag() == SpannableHelper.CLICKABLE_SPAN) {
@ -1340,7 +1487,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} else {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = results.statuses.get(0);
@ -1446,11 +1593,11 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
builderInner.show();
} else if (itemId == R.id.action_schedule_boost) {
MastodonHelper.scheduleBoost(context, MastodonHelper.ScheduleType.BOOST, statusToDeal, null, null);
} else if (itemId == R.id.action_admin) {
/* Intent intent = new Intent(context, AccountReportActivity.class);
} /*else if (itemId == R.id.action_admin) {
Intent intent = new Intent(context, AccountReportActivity.class);
intent.putExtra(Helper.ARG_ACCOUNT, statusToDeal.account);
context.startActivity(intent);*/
} else if (itemId == R.id.action_open_browser) {
context.startActivity(intent);
} */ else if (itemId == R.id.action_open_browser) {
Helper.openBrowser(context, statusToDeal.url);
} else if (itemId == R.id.action_remove) {
AlertDialog.Builder builderInner = new AlertDialog.Builder(context, Helper.dialogStyle());
@ -1540,12 +1687,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
if (translate.getTranslatedContent() != null) {
statusToDeal.translationShown = true;
statusToDeal.translationContent = translate.getTranslatedContent();
new Thread(() -> {
SpannableHelper.convertStatus(context.getApplicationContext(), statusToDeal);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
mainHandler.post(myRunnable);
}).start();
adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal));
} else {
Toasty.error(context, context.getString(R.string.toast_error_translate), Toast.LENGTH_LONG).show();
}
@ -1631,7 +1773,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.actionButtonReply.setOnClickListener(v -> {
if (remote) {
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.url, null, "statuses", false, true, false, 0, null, null, 1)
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.uri, null, "statuses", false, true, false, 0, null, null, 1)
.observe((LifecycleOwner) context, results -> {
if (results.statuses != null && results.statuses.size() > 0) {
Status fetchedStatus = statusList.get(0);
@ -1657,6 +1799,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.bindingReport.checkbox.setChecked(status.isChecked);
holder.bindingReport.checkbox.setOnClickListener(v -> status.isChecked = !status.isChecked);
}
}
private static boolean mediaObfuscated(Status status) {
@ -1723,6 +1866,34 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
return position;
}
/**
* Will manage the current position of the element in the adapter. Action is async, and position might have changed
*
* @param notificationList List<Notification> - Not null when calling from notification adapter
* @param statusList ist<Status> statusList - Not null when calling from status adapter
* @param id String - Current status
* @return int - position in real time
*/
public static int getPositionAsync(List<Notification> notificationList, List<Status> statusList, String id) {
int position = 0;
if (statusList != null) {
for (Status _status : statusList) {
if (id != null && ((_status.id != null && _status.id.compareTo(id) == 0) || (_status.reblog != null && _status.reblog.id != null && _status.reblog.id.compareTo(id) == 0))) {
break;
}
position++;
}
} else if (notificationList != null) {
for (Notification notification : notificationList) {
if (notification.status != null && notification.status.id.compareTo(id) == 0) {
break;
}
position++;
}
}
return position;
}
@Override
public int getItemViewType(int position) {
if (timelineType == Timeline.TimeLineEnum.ART) {
@ -1783,36 +1954,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, timelineType, minified, canBeFederated);
if (holder.timer != null) {
holder.timer.cancel();
holder.timer = null;
}
if (holder.dateTimer != null) {
holder.dateTimer.cancel();
holder.dateTimer = null;
}
if (status.emojis != null && status.emojis.size() > 0) {
holder.timer = new Timer();
holder.timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> holder.binding.statusContent.invalidate();
mainHandler.post(myRunnable);
}
}, 100, 100);
}
holder.dateTimer = new Timer();
holder.dateTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> holder.binding.dateShort.setText(Helper.dateDiff(context, status.created_at));
mainHandler.post(myRunnable);
}
}, 100, 10000);
} else if (viewHolder.getItemViewType() == STATUS_ART) {
StatusViewHolder holder = (StatusViewHolder) viewHolder;
MastodonHelper.loadPPMastodon(holder.bindingArt.artPp, status.account);
@ -1820,7 +1961,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
.load(status.art_attachment.preview_url)
.apply(new RequestOptions().transform(new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
.into(holder.bindingArt.artMedia);
holder.bindingArt.artAcct.setText(status.account.span_display_name, TextView.BufferType.SPANNABLE);
holder.bindingArt.artAcct.setText(
status.account.getSpanDisplayName(context,
new WeakReference<>(holder.bindingArt.artAcct)),
TextView.BufferType.SPANNABLE);
holder.bindingArt.artUsername.setText(String.format(Locale.getDefault(), "@%s", status.account.acct));
holder.bindingArt.artPp.setOnClickListener(v -> {
Intent intent = new Intent(context, ProfileActivity.class);
@ -1878,12 +2022,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
super.onViewRecycled(holder);
if (holder instanceof StatusViewHolder && ((StatusViewHolder) holder).timer != null) {
((StatusViewHolder) holder).timer.cancel();
}
if (holder instanceof StatusViewHolder && ((StatusViewHolder) holder).dateTimer != null) {
((StatusViewHolder) holder).dateTimer.cancel();
}
}
public interface FetchMoreCallBack {
@ -1899,8 +2037,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
DrawerFetchMoreBinding bindingFetchMore;
DrawerStatusNotificationBinding bindingNotification;
DrawerStatusArtBinding bindingArt;
Timer timer;
Timer dateTimer;
StatusViewHolder(DrawerStatusBinding itemView) {
super(itemView.getRoot());

View file

@ -122,7 +122,7 @@ public class FragmentMedia extends Fragment {
binding.mediaPicture.setVisibility(View.VISIBLE);
binding.mediaPicture.setTransitionName(attachment.url);
if (Helper.isValidContextForGlide(requireActivity())) {
if (Helper.isValidContextForGlide(requireActivity()) && isAdded()) {
Glide.with(requireActivity())
.asBitmap()
.dontTransform()
@ -142,7 +142,7 @@ public class FragmentMedia extends Fragment {
binding.mediaPicture.setVisibility(View.VISIBLE);
binding.pbarInf.setIndeterminate(true);
binding.loader.setVisibility(View.VISIBLE);
if (Helper.isValidContextForGlide(requireActivity())) {
if (Helper.isValidContextForGlide(requireActivity()) && isAdded()) {
Glide.with(requireActivity())
.asBitmap()
.dontTransform()
@ -312,7 +312,6 @@ public class FragmentMedia extends Fragment {
timer.cancel();
timer = null;
}
binding = null;
}
@Override

View file

@ -14,11 +14,14 @@ package app.fedilab.android.ui.fragment.settings;
* 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.annotation.SuppressLint;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import app.fedilab.android.R;
import app.fedilab.android.helper.Helper;
@ -31,16 +34,28 @@ public class FragmentLanguageSettings extends PreferenceFragmentCompat implement
createPref();
}
@SuppressLint("ApplySharedPref")
private void createPref() {
ListPreference SET_DEFAULT_LOCALE_NEW = findPreference(getString(R.string.SET_DEFAULT_LOCALE_NEW));
if (SET_DEFAULT_LOCALE_NEW != null) {
SET_DEFAULT_LOCALE_NEW.getContext().setTheme(Helper.dialogStyle());
}
Preference SET_TRANSLATE_VALUES_RESET = findPreference(getString(R.string.SET_TRANSLATE_VALUES_RESET));
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
if (SET_TRANSLATE_VALUES_RESET != null) {
SET_TRANSLATE_VALUES_RESET.setOnPreferenceClickListener(preference -> {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(getString(R.string.SET_DEFAULT_LOCALE_NEW), null);
editor.commit();
return true;
});
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.compareToIgnoreCase(getString(R.string.SET_DEFAULT_LOCALE_NEW)) == 0) {
if (key.compareToIgnoreCase(getString(R.string.SET_DEFAULT_LOCALE_NEW)) == 0 || key.compareToIgnoreCase(getString(R.string.SET_TRANSLATE_VALUES_RESET)) == 0) {
requireActivity().recreate();
Helper.recreateMainActivity(requireActivity());
}

View file

@ -21,8 +21,6 @@ import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -45,7 +43,6 @@ import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.FragmentPaginationBinding;
import app.fedilab.android.helper.DividerDecoration;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.StatusAdapter;
import app.fedilab.android.viewmodel.mastodon.StatusesVM;
@ -98,26 +95,19 @@ public class FragmentMastodonContext extends Fragment {
}
} else if (statusPosted != null && statusAdapter != null) {
if (requireActivity() instanceof ContextActivity) {
new Thread(() -> {
Status convertStatus = SpannableHelper.convertStatus(context, statusPosted);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
int i = 0;
for (Status status : statuses) {
if (status.id.equals(convertStatus.in_reply_to_id)) {
statuses.add((i + 1), convertStatus);
statusAdapter.notifyItemInserted((i + 1));
if (requireActivity() instanceof ContextActivity) {
//Redraw decorations
statusAdapter.notifyItemRangeChanged(0, statuses.size());
}
break;
}
i++;
int i = 0;
for (Status status : statuses) {
if (status.id.equals(statusPosted.in_reply_to_id)) {
statuses.add((i + 1), statusPosted);
statusAdapter.notifyItemInserted((i + 1));
if (requireActivity() instanceof ContextActivity) {
//Redraw decorations
statusAdapter.notifyItemRangeChanged(0, statuses.size());
}
};
mainHandler.post(myRunnable);
}).start();
break;
}
i++;
}
}
}
}

View file

@ -14,17 +14,14 @@ package app.fedilab.android.ui.fragment.timeline;
* 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.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.service.notification.StatusBarNotification;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -178,22 +175,6 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
}
@Override
public void onResume() {
super.onResume();
NotificationManager mNotificationManager = (NotificationManager) requireActivity().getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && BaseMainActivity.currentAccount != null && BaseMainActivity.currentAccount.mastodon_account != null) {
for (StatusBarNotification statusBarNotification : mNotificationManager.getActiveNotifications()) {
if ((BaseMainActivity.currentAccount.mastodon_account.acct + "@" + BaseMainActivity.currentAccount.instance).equals(statusBarNotification.getGroupKey())) {
mNotificationManager.cancel(statusBarNotification.getId());
}
}
} else {
mNotificationManager.cancelAll();
}
}
/**
* Intialize the view for notifications
*
@ -456,25 +437,28 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
}
int position = 0;
//We loop through messages already in the timeline
for (Notification notificationsAlreadyPresent : this.notificationList) {
//We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position
//Pinned messages are ignored because their date can be older
if (notificationReceived.id.compareTo(notificationsAlreadyPresent.id) > 0) {
if (this.notificationList != null) {
notificationAdapter.notifyItemRangeChanged(0, this.notificationList.size());
for (Notification notificationsAlreadyPresent : this.notificationList) {
//We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position
//Pinned messages are ignored because their date can be older
if (notificationReceived.id.compareTo(notificationsAlreadyPresent.id) > 0) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedNotifications.add(notificationReceived.id);
this.notificationList.add(position, notificationReceived);
notificationAdapter.notifyItemInserted(position);
break;
}
position++;
}
//Statuses added at the bottom, we flag them by position = -2 for not dealing with them and fetch more
if (position == this.notificationList.size()) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedNotifications.add(notificationReceived.id);
this.notificationList.add(position, notificationReceived);
notificationAdapter.notifyItemInserted(position);
break;
return NOTIFICATION__AT_THE_BOTTOM;
}
position++;
}
//Statuses added at the bottom, we flag them by position = -2 for not dealing with them and fetch more
if (position == this.notificationList.size()) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedNotifications.add(notificationReceived.id);
this.notificationList.add(position, notificationReceived);
notificationAdapter.notifyItemInserted(position);
return NOTIFICATION__AT_THE_BOTTOM;
}
return position;
}
@ -491,8 +475,8 @@ public class FragmentMastodonNotification extends Fragment implements Notificati
}
}).start();
}
super.onDestroyView();
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action);
super.onDestroyView();
}
@Override

View file

@ -30,7 +30,6 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@ -57,7 +56,6 @@ import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.databinding.FragmentPaginationBinding;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.StatusAdapter;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
@ -119,15 +117,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
statusAdapter.notifyItemRemoved(position);
}
} else if (statusPosted != null && statusAdapter != null && timelineType == Timeline.TimeLineEnum.HOME) {
new Thread(() -> {
Status convertStatus = SpannableHelper.convertStatus(context, statusPosted);
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
statuses.add(0, convertStatus);
statusAdapter.notifyItemInserted(0);
};
mainHandler.post(myRunnable);
}).start();
statuses.add(0, statusPosted);
statusAdapter.notifyItemInserted(0);
}
}
}
@ -184,15 +175,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
public void scrollToTop() {
if (binding != null) {
binding.recyclerView.scrollToPosition(0);
binding.swipeContainer.setRefreshing(true);
flagLoading = false;
route(DIRECTION.SCROLL_TOP, true);
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
@ -437,6 +425,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (direction == DIRECTION.BOTTOM) {
flagLoading = true;
}
if (direction == DIRECTION.SCROLL_TOP) {
binding.recyclerView.scrollToPosition(0);
}
}
/**
@ -473,7 +464,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
statusFetchMore.isFetchMore = true;
statusFetchMore.id = Helper.generateString();
int insertAt;
if (direction == DIRECTION.REFRESH || direction == DIRECTION.BOTTOM) {
if (direction == DIRECTION.REFRESH || direction == DIRECTION.BOTTOM || direction == DIRECTION.SCROLL_TOP) {
insertAt = lastInsertedPosition;
} else {
insertAt = initialInsertedPosition;
@ -499,27 +490,31 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
return STATUS_PRESENT;
}
int position = 0;
//We loop through messages already in the timeline
for (Status statusAlreadyPresent : this.statuses) {
//We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position
//Pinned messages are ignored because their date can be older
if (statusReceived.id.compareTo(statusAlreadyPresent.id) > 0 && !statusAlreadyPresent.pinned) {
if (this.statuses != null) {
statusAdapter.notifyItemRangeChanged(0, this.statuses.size());
//We loop through messages already in the timeline
for (Status statusAlreadyPresent : this.statuses) {
//We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position
//Pinned messages are ignored because their date can be older
if (statusReceived.id.compareTo(statusAlreadyPresent.id) > 0 && !statusAlreadyPresent.pinned) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedStatuses.add(statusReceived.id);
this.statuses.add(position, statusReceived);
statusAdapter.notifyItemInserted(position);
break;
}
position++;
}
//Statuses added at the bottom, we flag them by position = -2 for not dealing with them and fetch more
if (position == this.statuses.size()) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedStatuses.add(statusReceived.id);
this.statuses.add(position, statusReceived);
statusAdapter.notifyItemInserted(position);
break;
return STATUS_AT_THE_BOTTOM;
}
position++;
}
//Statuses added at the bottom, we flag them by position = -2 for not dealing with them and fetch more
if (position == this.statuses.size()) {
//We add the status to a list of id - thus we know it is already in the timeline
idOfAddedStatuses.add(statusReceived.id);
this.statuses.add(position, statusReceived);
statusAdapter.notifyItemInserted(position);
return STATUS_AT_THE_BOTTOM;
}
return position;
}
@ -627,11 +622,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -647,11 +642,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getPublic(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, false, true, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -669,11 +664,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, null)
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -692,11 +687,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getMisskey(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -713,11 +708,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false));
} else if (direction == DIRECTION.TOP) {
flagLoading = false;
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getPeertube(remoteInstance, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -733,11 +728,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (direction == DIRECTION.TOP) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getPublic(null, remoteInstance, true, false, false, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -754,11 +749,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (direction == DIRECTION.TOP) {
timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, list_id, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -778,11 +773,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (direction == DIRECTION.TOP) {
timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, fetchingMissing ? min_id_fetch_more : min_id, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.TOP, fetchingMissing));
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getHashTag(BaseMainActivity.currentToken, BaseMainActivity.currentInstance, tagTimeline.name, false, tagTimeline.isART, tagTimeline.all, tagTimeline.any, tagTimeline.none, null, null, null, MastodonHelper.statusesPerCall(requireActivity()))
.observe(getViewLifecycleOwner(), statusesRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusesRefresh, direction, true);
} else {
initializeStatusesCommonView(statusesRefresh);
}
@ -964,11 +959,11 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
.observe(getViewLifecycleOwner(), statusesTop -> dealWithPagination(statusesTop, DIRECTION.TOP, true));
}
} else if (direction == DIRECTION.REFRESH) {
} else if (direction == DIRECTION.REFRESH || direction == DIRECTION.SCROLL_TOP) {
timelinesVM.getHome(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, true, null, null, null, MastodonHelper.statusesPerCall(requireActivity()), false)
.observe(getViewLifecycleOwner(), statusRefresh -> {
if (statusAdapter != null) {
dealWithPagination(statusRefresh, DIRECTION.REFRESH, true);
dealWithPagination(statusRefresh, direction, true);
} else {
initializeStatusesCommonView(statusRefresh);
}
@ -1029,6 +1024,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
public enum DIRECTION {
TOP,
BOTTOM,
REFRESH
REFRESH,
SCROLL_TOP
}
}

View file

@ -14,15 +14,17 @@ package app.fedilab.android.ui.fragment.timeline;
* 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.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.res.ResourcesCompat;
@ -238,6 +240,20 @@ public class FragmentNotificationContainer extends Fragment {
return binding.getRoot();
}
@Override
public void onResume() {
super.onResume();
NotificationManager mNotificationManager = (NotificationManager) requireActivity().getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && BaseMainActivity.currentAccount != null && BaseMainActivity.currentAccount.mastodon_account != null) {
for (StatusBarNotification statusBarNotification : mNotificationManager.getActiveNotifications()) {
if (statusBarNotification.getGroupKey().contains(BaseMainActivity.currentAccount.mastodon_account.acct + "@" + BaseMainActivity.currentAccount.instance)) {
mNotificationManager.cancel(statusBarNotification.getId());
}
}
} else {
mNotificationManager.cancelAll();
}
}
public void scrollToTop() {
if (binding != null) {
@ -251,11 +267,5 @@ public class FragmentNotificationContainer extends Fragment {
}
}
@SuppressLint("ApplySharedPref")
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
}

View file

@ -52,7 +52,6 @@ import app.fedilab.android.client.entities.api.Tag;
import app.fedilab.android.client.entities.api.Token;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import retrofit2.Call;
@ -306,9 +305,6 @@ public class AccountsVM extends AndroidViewModel {
}
}
Account finalAccount = account;
if (finalAccount != null) {
SpannableHelper.convertAccount(getApplication().getApplicationContext(), finalAccount);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> accountMutableLiveData.setValue(finalAccount);
mainHandler.post(myRunnable);
@ -342,7 +338,7 @@ public class AccountsVM extends AndroidViewModel {
try {
Response<List<Status>> accountStatusesResponse = accountStatusesCall.execute();
if (accountStatusesResponse.isSuccessful()) {
statusList = SpannableHelper.convertStatus(getApplication().getApplicationContext(), accountStatusesResponse.body());
statusList = accountStatusesResponse.body();
pagination = MastodonHelper.getPagination(accountStatusesResponse.headers());
}
@ -378,7 +374,7 @@ public class AccountsVM extends AndroidViewModel {
try {
Response<List<Account>> followersResponse = followersCall.execute();
if (followersResponse.isSuccessful()) {
accountList = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), followersResponse.body());
accountList = followersResponse.body();
pagination = MastodonHelper.getPagination(followersResponse.headers());
}
} catch (Exception e) {
@ -414,7 +410,7 @@ public class AccountsVM extends AndroidViewModel {
try {
Response<List<Account>> followingResponse = followingCall.execute();
if (followingResponse.isSuccessful()) {
accountList = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), followingResponse.body());
accountList = followingResponse.body();
pagination = MastodonHelper.getPagination(followingResponse.headers());
}
} catch (Exception e) {
@ -883,11 +879,6 @@ public class AccountsVM extends AndroidViewModel {
}
}
List<Account> finalAccountList = accountList;
if (finalAccountList != null) {
for (Account account : finalAccountList) {
SpannableHelper.convertAccount(getApplication().getApplicationContext(), account);
}
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> accountListMutableLiveData.setValue(finalAccountList);
mainHandler.post(myRunnable);
@ -912,7 +903,7 @@ public class AccountsVM extends AndroidViewModel {
Response<List<Status>> bookmarksResponse = bookmarksCall.execute();
if (bookmarksResponse.isSuccessful()) {
statusList = bookmarksResponse.body();
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusList);
statuses.statuses = statusList;
statuses.pagination = MastodonHelper.getPagination(bookmarksResponse.headers());
}
} catch (Exception e) {
@ -943,7 +934,7 @@ public class AccountsVM extends AndroidViewModel {
Response<List<Status>> favouritesResponse = favouritesCall.execute();
if (favouritesResponse.isSuccessful()) {
statusList = favouritesResponse.body();
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusList);
statuses.statuses = statusList;
statuses.pagination = MastodonHelper.getPagination(favouritesResponse.headers());
}
} catch (Exception e) {
@ -975,7 +966,7 @@ public class AccountsVM extends AndroidViewModel {
Response<List<Account>> mutesResponse = mutesCall.execute();
if (mutesResponse.isSuccessful()) {
accountList = mutesResponse.body();
accounts.accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), accountList);
accounts.accounts = accountList;
accounts.pagination = MastodonHelper.getPagination(mutesResponse.headers());
}
} catch (Exception e) {
@ -1007,7 +998,7 @@ public class AccountsVM extends AndroidViewModel {
Response<List<Account>> blocksResponse = blocksCall.execute();
if (blocksResponse.isSuccessful()) {
accountList = blocksResponse.body();
accounts.accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), accountList);
accounts.accounts = accountList;
accounts.pagination = MastodonHelper.getPagination(blocksResponse.headers());
}
} catch (Exception e) {
@ -1303,7 +1294,7 @@ public class AccountsVM extends AndroidViewModel {
Response<List<Account>> followRequestsResponse = followRequestsCall.execute();
if (followRequestsResponse.isSuccessful()) {
accountList = followRequestsResponse.body();
accounts.accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), accountList);
accounts.accounts = accountList;
accounts.pagination = MastodonHelper.getPagination(followRequestsResponse.headers());
}
} catch (Exception e) {

View file

@ -23,16 +23,12 @@ import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.client.endpoints.MastodonAnnouncementsService;
import app.fedilab.android.client.entities.api.Announcement;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.SpannableHelper;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
@ -55,7 +51,6 @@ public class AnnouncementsVM extends AndroidViewModel {
}
private MastodonAnnouncementsService init(@NonNull String instance) {
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance + "/api/v1/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
@ -83,7 +78,6 @@ public class AnnouncementsVM extends AndroidViewModel {
Response<List<Announcement>> getAnnouncementsResponse = getAnnouncementsCall.execute();
if (getAnnouncementsResponse.isSuccessful()) {
announcementList = getAnnouncementsResponse.body();
SpannableHelper.convertAnnouncement(getApplication(), announcementList);
}
} catch (Exception e) {
e.printStackTrace();

View file

@ -35,7 +35,6 @@ import app.fedilab.android.client.entities.api.Notifications;
import app.fedilab.android.client.entities.api.PushSubscription;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.TimelineHelper;
import okhttp3.OkHttpClient;
import retrofit2.Call;
@ -103,15 +102,6 @@ public class NotificationsVM extends AndroidViewModel {
if (notificationsResponse.isSuccessful()) {
List<Notification> notFilteredNotifications = notificationsResponse.body();
notifications.notifications = TimelineHelper.filterNotification(getApplication().getApplicationContext(), notFilteredNotifications);
if (notifications.notifications != null) {
for (Notification notification : notifications.notifications) {
if (notification != null) {
if (notification.status != null) {
notification.status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), notification.status);
}
}
}
}
notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers());
}
} catch (Exception e) {
@ -147,9 +137,6 @@ public class NotificationsVM extends AndroidViewModel {
Response<Notification> notificationResponse = notificationCall.execute();
if (notificationResponse.isSuccessful()) {
notification = notificationResponse.body();
if (notification != null) {
notification.status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), notification.status);
}
}
} catch (Exception e) {
e.printStackTrace();

View file

@ -36,7 +36,6 @@ import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.SpannableHelper;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
@ -112,13 +111,9 @@ public class SearchVM extends AndroidViewModel {
if (results != null) {
if (results.statuses == null) {
results.statuses = new ArrayList<>();
} else {
results.statuses = SpannableHelper.convertStatus(getApplication(), results.statuses);
}
if (results.accounts == null) {
results.accounts = new ArrayList<>();
} else {
results.accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), results.accounts);
}
if (results.hashtags == null) {
results.hashtags = new ArrayList<>();
@ -144,7 +139,6 @@ public class SearchVM extends AndroidViewModel {
try {
results.statuses = new ArrayList<>();
List<Status> statuses = new StatusCache(getApplication()).searchStatus(StatusCache.CacheEnum.HOME, instance, userId, q);
statuses = SpannableHelper.convertStatus(getApplication(), statuses);
results.statuses.addAll(statuses);
} catch (DBException e) {
e.printStackTrace();

View file

@ -47,7 +47,6 @@ import app.fedilab.android.client.entities.app.StatusCache;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.TimelineHelper;
import okhttp3.Headers;
import okhttp3.MultipartBody;
@ -279,7 +278,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
}
} catch (Exception e) {
e.printStackTrace();
@ -356,13 +355,7 @@ public class StatusesVM extends AndroidViewModel {
context = contextResponse.body();
if (context != null) {
TimelineHelper.filterStatus(getApplication().getApplicationContext(), context.descendants, TimelineHelper.FilterTimeLineType.CONTEXT);
for (Status status : context.descendants) {
SpannableHelper.convertStatus(getApplication().getApplicationContext(), status);
}
TimelineHelper.filterStatus(getApplication().getApplicationContext(), context.ancestors, TimelineHelper.FilterTimeLineType.CONTEXT);
for (Status status : context.ancestors) {
SpannableHelper.convertStatus(getApplication().getApplicationContext(), status);
}
}
}
@ -406,7 +399,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<List<Account>> accountsResponse = accountsCall.execute();
if (accountsResponse.isSuccessful()) {
accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), accountsResponse.body());
accounts = accountsResponse.body();
}
headers = accountsResponse.headers();
} catch (Exception e) {
@ -451,7 +444,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<List<Account>> accountsResponse = accountsCall.execute();
if (accountsResponse.isSuccessful()) {
accounts = SpannableHelper.convertAccounts(getApplication().getApplicationContext(), accountsResponse.body());
accounts = accountsResponse.body();
}
headers = accountsResponse.headers();
} catch (Exception e) {
@ -461,7 +454,9 @@ public class StatusesVM extends AndroidViewModel {
Handler mainHandler = new Handler(Looper.getMainLooper());
Accounts accountsPagination = new Accounts();
accountsPagination.accounts = accounts;
accountsPagination.pagination = MastodonHelper.getPagination(headers);
if (headers != null) {
accountsPagination.pagination = MastodonHelper.getPagination(headers);
}
Runnable myRunnable = () -> accountsMutableLiveData.setValue(accountsPagination);
mainHandler.post(myRunnable);
}).start();
@ -487,7 +482,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -531,7 +526,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -577,7 +572,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -621,7 +616,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -665,7 +660,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -709,7 +704,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -753,7 +748,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -797,7 +792,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -841,7 +836,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();
@ -885,7 +880,7 @@ public class StatusesVM extends AndroidViewModel {
try {
Response<Status> statusResponse = statusCall.execute();
if (statusResponse.isSuccessful()) {
status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusResponse.body());
status = statusResponse.body();
} else {
if (statusResponse.errorBody() != null) {
errorMessage = statusResponse.errorBody().string();

View file

@ -48,7 +48,6 @@ import app.fedilab.android.client.entities.peertube.PeertubeVideo;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.MastodonHelper;
import app.fedilab.android.helper.SpannableHelper;
import app.fedilab.android.helper.TimelineHelper;
import okhttp3.OkHttpClient;
import retrofit2.Call;
@ -121,7 +120,7 @@ public class TimelinesVM extends AndroidViewModel {
Response<List<Status>> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
statusList = publicTlResponse.body();
statusList = SpannableHelper.convertStatus(getApplication().getApplicationContext(), statusList);
statusList = statusList;
}
} catch (Exception e) {
e.printStackTrace();
@ -190,8 +189,7 @@ public class TimelinesVM extends AndroidViewModel {
Response<List<Status>> publicTlResponse = publicTlCall.execute();
if (publicTlResponse.isSuccessful()) {
List<Status> notFilteredStatuses = publicTlResponse.body();
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.statuses = TimelineHelper.filterStatus(getApplication(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.pagination = MastodonHelper.getPagination(publicTlResponse.headers());
}
} catch (Exception e) {
@ -219,7 +217,6 @@ public class TimelinesVM extends AndroidViewModel {
statusesMutableLiveData = new MutableLiveData<>();
new Thread(() -> {
Call<Nitter> publicTlCall = mastodonTimelinesService.getNitter(accountsStr, max_position);
Statuses statuses = new Statuses();
if (publicTlCall != null) {
try {
@ -233,8 +230,10 @@ public class TimelinesVM extends AndroidViewModel {
statusList.add(status);
}
}
statuses.statuses = SpannableHelper.convertNitterStatus(getApplication().getApplicationContext(), statusList);
statuses.pagination = MastodonHelper.getPagination(publicTlResponse.headers());
statuses.statuses = statusList;
String max_id = publicTlResponse.headers().get("min-id");
statuses.pagination = new Pagination();
statuses.pagination.max_id = max_id;
}
} catch (Exception e) {
e.printStackTrace();
@ -273,12 +272,11 @@ public class TimelinesVM extends AndroidViewModel {
List<Status> statusList = new ArrayList<>();
if (misskeyNoteList != null) {
for (MisskeyNote misskeyNote : misskeyNoteList) {
Status status = MisskeyNote.convert(misskeyNote);
Status status = MisskeyNote.convert(misskeyNote, instance);
statusList.add(status);
}
}
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.statuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.pagination = new Pagination();
if (statusList.size() > 0) {
statuses.pagination.min_id = statusList.get(0).id;
@ -324,8 +322,7 @@ public class TimelinesVM extends AndroidViewModel {
statusList.add(status);
}
}
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.statuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.pagination = new Pagination();
if (statusList.size() > 0) {
//These values are not used.
@ -400,14 +397,14 @@ public class TimelinesVM extends AndroidViewModel {
MastodonTimelinesService mastodonTimelinesService = init(instance);
new Thread(() -> {
Statuses statuses = new Statuses();
Call<List<Status>> hashTagTlCall = mastodonTimelinesService.getHashTag(token, hashtag, local, onlyMedia, all, any, none, maxId, sinceId, minId, limit);
String hashtagTrim = hashtag.replaceAll("\\#", "");
Call<List<Status>> hashTagTlCall = mastodonTimelinesService.getHashTag(token, hashtagTrim, local, onlyMedia, all, any, none, maxId, sinceId, minId, limit);
if (hashTagTlCall != null) {
try {
Response<List<Status>> hashTagTlResponse = hashTagTlCall.execute();
if (hashTagTlResponse.isSuccessful()) {
List<Status> notFilteredStatuses = hashTagTlResponse.body();
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.PUBLIC);
statuses.pagination = MastodonHelper.getPagination(hashTagTlResponse.headers());
}
} catch (Exception e) {
@ -449,8 +446,7 @@ public class TimelinesVM extends AndroidViewModel {
Response<List<Status>> homeTlResponse = homeTlCall.execute();
if (homeTlResponse.isSuccessful()) {
List<Status> notFilteredStatuses = homeTlResponse.body();
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.HOME);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), notFilteredStatuses, TimelineHelper.FilterTimeLineType.HOME);
statuses.pagination = MastodonHelper.getPagination(homeTlResponse.headers());
if (!fetchingMissing) {
for (Status status : statuses.statuses) {
@ -502,8 +498,7 @@ public class TimelinesVM extends AndroidViewModel {
statuses = statusCacheDAO.geStatuses(StatusCache.CacheEnum.HOME, instance, user_id, maxId, minId, sinceId);
if (statuses != null) {
List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statuses.statuses, TimelineHelper.FilterTimeLineType.HOME);
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses);
statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statuses.statuses, TimelineHelper.FilterTimeLineType.HOME);
if (statuses.statuses != null && statuses.statuses.size() > 0) {
statuses.pagination = new Pagination();
statuses.pagination.min_id = statuses.statuses.get(0).id;
@ -570,7 +565,7 @@ public class TimelinesVM extends AndroidViewModel {
try {
Response<List<Status>> listTlResponse = listTlCall.execute();
if (listTlResponse.isSuccessful()) {
statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), listTlResponse.body());
statuses.statuses = listTlResponse.body();
statuses.pagination = MastodonHelper.getPagination(listTlResponse.headers());
}
} catch (Exception e) {
@ -606,11 +601,6 @@ public class TimelinesVM extends AndroidViewModel {
Response<List<Conversation>> conversationsResponse = conversationsCall.execute();
if (conversationsResponse.isSuccessful()) {
conversations.conversations = conversationsResponse.body();
if (conversations.conversations != null) {
for (Conversation conversation : conversations.conversations) {
conversation.last_status = SpannableHelper.convertStatus(getApplication().getApplicationContext(), conversation.last_status);
}
}
conversations.pagination = MastodonHelper.getPagination(conversationsResponse.headers());
}
} catch (Exception e) {

View file

@ -0,0 +1,101 @@
package app.fedilab.android.viewmodel.pleroma;
/* 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 android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import java.util.List;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.client.endpoints.PleromaAPI;
import app.fedilab.android.client.entities.api.Announcement;
import app.fedilab.android.helper.Helper;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ActionsVM extends AndroidViewModel {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(getApplication().getApplicationContext()))
.build();
private MutableLiveData<Announcement> announcementMutableLiveData;
private MutableLiveData<List<Announcement>> announcementListMutableLiveData;
public ActionsVM(@NonNull Application application) {
super(application);
}
private PleromaAPI init(@NonNull String instance) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://" + instance + "/api/v1/")
.addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder()))
.client(okHttpClient)
.build();
return retrofit.create(PleromaAPI.class);
}
/**
* React to an announcement with an emoji.
*
* @param instance Instance domain of the active account
* @param token Access token of the active account
* @param id Local ID of an announcement
* @param name Unicode emoji, or shortcode of custom emoji
*/
public void addReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) {
PleromaAPI pleromaAPI = init(instance);
new Thread(() -> {
Call<Void> addReactionCall = pleromaAPI.addReaction(token, id, name);
if (addReactionCall != null) {
try {
addReactionCall.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
/**
* Undo a react emoji to an announcement.
*
* @param instance Instance domain of the active account
* @param token Access token of the active account
* @param id Local ID of an announcement
* @param name Unicode emoji, or shortcode of custom emoji
*/
public void removeReaction(@NonNull String instance, String token, @NonNull String id, @NonNull String name) {
PleromaAPI pleromaAPI = init(instance);
new Thread(() -> {
Call<Void> removeReactionCall = pleromaAPI.removeReaction(token, id, name);
if (removeReactionCall != null) {
try {
removeReactionCall.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}

View file

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

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM18.92,8h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2L4.26,14zM5.08,16h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zM8.03,8L5.08,8c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14L9.66,14c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zM14.59,19.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z" />
</vector>

View file

@ -16,12 +16,27 @@
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/set_account"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:text="@string/account"
android:textAlignment="textStart"
android:textColor="@color/cyanea_accent_dark_reference"
app:icon="@drawable/ic_baseline_navigate_next_24"
app:iconGravity="end"
app:iconTint="@color/cyanea_accent_dark_reference"
app:strokeColor="@color/cyanea_accent_dark_reference" />
<com.google.android.material.button.MaterialButton
android:id="@+id/set_timelines"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="12dp"
android:layout_marginTop="24dp"
android:text="@string/settings_category_label_timelines"
android:textAlignment="textStart"
android:textColor="@color/cyanea_accent_dark_reference"

View file

@ -59,64 +59,9 @@
tools:maxLines="10"
tools:text="@tools:sample/lorem/random" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_reactions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:paddingBottom="10dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/reactions_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_add_custom_emoji"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/add_reaction"
android:src="@drawable/ic_baseline_emoji_emotions_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/iconColor" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_emoji"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/add_reaction"
android:src="@drawable/ic_baseline_add_reaction_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/iconColor" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<app.fedilab.android.helper.FedilabAutoCompleteTextView
android:id="@+id/fake_edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAutofill="noExcludeDescendants"
android:inputType="text" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
<include
android:id="@+id/layout_reactions"
layout="@layout/layout_reactions" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -24,7 +24,7 @@
android:layout_marginTop="@dimen/card_margin"
android:clipChildren="false"
android:clipToPadding="false"
app:cardElevation="2dp">
app:cardElevation="0dp">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
@ -34,6 +34,9 @@
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_booster_info"
@ -77,10 +80,6 @@
tools:text="@tools:sample/full_names" />
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.divider.MaterialDivider
android:id="@+id/booster_divider"
android:layout_width="match_parent"
android:layout_height="1dp" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/header_container"
@ -151,12 +150,19 @@
android:maxLines="1"
tools:text="@tools:sample/full_names" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/visibility_small"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="5dp"
android:src="@drawable/ic_baseline_public_24" />
<TextView
android:id="@+id/date_short"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="10dp" />
tools:text="2m" />
</androidx.appcompat.widget.LinearLayoutCompat>
@ -196,9 +202,6 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/spoiler"
@ -492,6 +495,27 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/action_share_container"
android:layout_width="match_parent"
android:layout_height="28dp"
android:layout_marginStart="48dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp"
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="end"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_share"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_baseline_share_24" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/action_buttons"
android:layout_width="match_parent"
@ -577,6 +601,12 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<include
android:id="@+id/layout_reactions"
layout="@layout/layout_reactions"
android:visibility="gone"
tools:visibility="visible" />
</androidx.appcompat.widget.LinearLayoutCompat>
</com.google.android.material.card.MaterialCardView>

View file

@ -19,12 +19,12 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/cardview_container"
android:layout_marginHorizontal="12dp"
android:backgroundTint="?backgroundColorLight"
android:layout_marginTop="12dp"
android:clipChildren="false"
android:clipToPadding="false"
app:cardElevation="2dp">
app:cardElevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View file

@ -19,6 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/cardview_container"
android:layout_marginHorizontal="12dp"
android:layout_marginTop="12dp"
android:clipChildren="false"

View file

@ -86,6 +86,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/join_mastodon"
style="@style/MyButtonColored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"

View file

@ -151,6 +151,7 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/signup"
style="@style/MyButtonColored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/sign_up" />

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/status_reactions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:orientation="horizontal"
android:paddingBottom="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/reactions_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_add_custom_emoji"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/add_reaction"
android:src="@drawable/ic_baseline_emoji_emotions_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/iconColor" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_emoji"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"
android:contentDescription="@string/add_reaction"
android:src="@drawable/ic_baseline_add_reaction_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tint="?attr/iconColor" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<app.fedilab.android.helper.FedilabAutoCompleteTextView
android:id="@+id/fake_edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAutofill="noExcludeDescendants"
android:inputType="text" />
</LinearLayout>
</androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -45,8 +45,8 @@
android:layout_gravity="center_vertical"
android:contentDescription="@string/profile_picture"
android:paddingTop="@dimen/nav_header_vertical_spacing"
tools:src="@tools:sample/avatars"
android:scaleType="fitCenter" />
android:scaleType="fitCenter"
tools:src="@tools:sample/avatars" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/change_account"

View file

@ -1,450 +1,452 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView 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:layout_width="match_parent"
android:id="@+id/popup_container"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_height="match_parent">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/background"
android:id="@+id/popup_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/fab_margin"
android:paddingTop="20dp"
android:paddingEnd="@dimen/fab_margin"
android:paddingBottom="20dp">
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardview_container"
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/card_margin"
android:layout_marginTop="@dimen/card_margin"
android:clipChildren="false"
android:clipToPadding="false"
app:cardElevation="2dp">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_user_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/avatar"
android:layout_width="20dp"
android:layout_height="20dp"
android:scaleType="centerInside"
tools:src="@drawable/ic_person" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:ellipsize="end"
android:maxLines="1"
tools:text="Display Name" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:alpha="0.7"
android:ellipsize="end"
android:maxLines="1"
tools:text="\@username\@instance.test" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_booster_info"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_boost_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:src="@drawable/ic_repeat" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_booster_avatar"
android:layout_width="20dp"
android:layout_height="20dp"
android:scaleType="centerInside"
android:src="@drawable/ic_person" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/spoiler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:text="Warning: Lorem Ipsum below"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/spoiler_expand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:text="@string/show_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?colorAccent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/status_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:text="@string/lorem_ipsum_text"
android:textIsSelectable="true"
tools:maxLines="10" />
<com.google.android.material.button.MaterialButton
android:id="@+id/toggle_truncate"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:text="@string/display_toot_truncate"
android:textAllCaps="false"
android:textSize="14sp"
android:visibility="gone"
app:strokeColor="@color/cyanea_accent_dark_reference" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container_trans"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical"
android:visibility="gone">
<View
android:id="@+id/translation_border_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@drawable/translation_border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/translation_border_top" />
<androidx.constraintlayout.widget.Placeholder
android:id="@+id/translation_border_top"
android:layout_width="wrap_content"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="@id/translation_label"
app:layout_constraintEnd_toEndOf="@id/translation_label"
app:layout_constraintTop_toTopOf="@id/translation_label" />
<TextView
android:id="@+id/translation_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:background="?backgroundColorLight"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:text="@string/translation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/status_content_translated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/translation_label"
app:layout_goneMarginBottom="8dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:paddingStart="@dimen/fab_margin"
android:paddingTop="20dp"
android:paddingEnd="@dimen/fab_margin"
android:paddingBottom="20dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
android:id="@+id/cardview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
app:cardCornerRadius="8dp"
android:layout_marginHorizontal="@dimen/card_margin"
android:layout_marginTop="@dimen/card_margin"
android:clipChildren="false"
android:clipToPadding="false"
app:cardElevation="2dp">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/card_image_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:visibility="gone"
tools:src="@tools:sample/backgrounds/scenic" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/card_image_vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/fedilab_logo_bubbles" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_user_info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:orientation="vertical"
android:layout_weight="1"
android:orientation="horizontal"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/card_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Maecenas sollicitudin, eros quis interdum posuere, quam lorem tempus dui."
android:textColor="?colorAccent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/avatar"
android:layout_width="20dp"
android:layout_height="20dp"
android:scaleType="centerInside"
tools:src="@drawable/ic_person" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/card_description"
android:layout_width="match_parent"
android:id="@+id/display_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:ellipsize="end"
android:maxLines="5"
android:text="@string/lorem_ipsum_text_card"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:lines="3" />
android:maxLines="1"
tools:text="Display Name" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/card_url"
android:layout_width="match_parent"
android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="https://domain.test"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textColor="?linkColor" />
android:layout_marginStart="6dp"
android:alpha="0.7"
android:ellipsize="end"
android:maxLines="1"
tools:text="\@username\@instance.test" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_booster_info"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_boost_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:scaleType="centerInside"
android:src="@drawable/ic_repeat" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_booster_avatar"
android:layout_width="20dp"
android:layout_height="20dp"
android:scaleType="centerInside"
android:src="@drawable/ic_person" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent"
android:layout_height="1dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/spoiler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:text="Warning: Lorem Ipsum below"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/spoiler_expand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:text="@string/show_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?colorAccent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/status_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:text="@string/lorem_ipsum_text"
android:textIsSelectable="true"
tools:maxLines="10" />
<com.google.android.material.button.MaterialButton
android:id="@+id/toggle_truncate"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="1dp"
android:paddingBottom="1dp"
android:text="@string/display_toot_truncate"
android:textAllCaps="false"
android:textSize="14sp"
android:visibility="gone"
app:strokeColor="@color/cyanea_accent_dark_reference" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container_trans"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="vertical"
android:visibility="gone">
<View
android:id="@+id/translation_border_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@drawable/translation_border"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/translation_border_top" />
<androidx.constraintlayout.widget.Placeholder
android:id="@+id/translation_border_top"
android:layout_width="wrap_content"
android:layout_height="1dp"
app:layout_constraintBottom_toBottomOf="@id/translation_label"
app:layout_constraintEnd_toEndOf="@id/translation_label"
app:layout_constraintTop_toTopOf="@id/translation_label" />
<TextView
android:id="@+id/translation_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:background="?backgroundColorLight"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:text="@string/translation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/status_content_translated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/translation_label"
app:layout_goneMarginBottom="8dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/card_image_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:visibility="gone"
tools:src="@tools:sample/backgrounds/scenic" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/card_image_vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/fedilab_logo_bubbles" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:orientation="vertical"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/card_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Maecenas sollicitudin, eros quis interdum posuere, quam lorem tempus dui."
android:textColor="?colorAccent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/card_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="5"
android:text="@string/lorem_ipsum_text_card"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:lines="3" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/card_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="https://domain.test"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textColor="?linkColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</com.google.android.material.card.MaterialCardView>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/media_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:visibility="gone" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:alpha="0.8"
android:gravity="center_vertical"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?statusTextColor"
tools:text="1 January 2021, 12:00" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/reblog_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/repeat_info"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
app:srcCompat="@drawable/ic_repeat" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reblogs_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:textColor="?statusTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/favourite_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/fav_info"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
app:srcCompat="@drawable/ic_star_outline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/favorites_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:textColor="?statusTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/visibility"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_baseline_public_24" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/action_buttons"
android:layout_width="match_parent"
android:layout_height="28dp"
android:layout_marginTop="6dp"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_reply"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="6dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_reply" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_favorite"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="12dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_star_outline" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_boost"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="12dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_repeat" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_more"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="6dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_more_horiz" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</com.google.android.material.card.MaterialCardView>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/media_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
android:layout_marginTop="6dp"
android:visibility="gone" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/status_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:alpha="0.8"
android:gravity="center_vertical"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?statusTextColor"
tools:text="1 January 2021, 12:00" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/reblog_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/repeat_info"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
app:srcCompat="@drawable/ic_repeat" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reblogs_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:textColor="?statusTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/favourite_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/fav_info"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
app:srcCompat="@drawable/ic_star_outline" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/favorites_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="10"
android:textColor="?statusTextColor" />
</androidx.appcompat.widget.LinearLayoutCompat>
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/visibility"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="12dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_baseline_public_24" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/action_buttons"
android:layout_width="match_parent"
android:layout_height="28dp"
android:layout_marginTop="6dp"
android:clipChildren="false"
android:clipToPadding="false">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_reply"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="6dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_reply" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_favorite"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="12dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_star_outline" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_boost"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginStart="12dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_repeat" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/action_button_more"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="6dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_more_horiz" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</com.google.android.material.card.MaterialCardView>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?backgroundColorLight"
android:orientation="vertical"
android:padding="@dimen/fab_margin">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="18sp"
android:textStyle="bold" />
android:background="?backgroundColorLight"
android:orientation="vertical"
android:padding="@dimen/fab_margin">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="18sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/select_theme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="10dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/select_theme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="10dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</ScrollView>

View file

@ -11,6 +11,11 @@
android:icon="@drawable/ic_baseline_contact_page_24"
android:title="@string/contact"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_language"
android:icon="@drawable/ic_baseline_language_24"
android:title="@string/languages"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_microphone"
android:icon="@drawable/ic_baseline_mic_24"

View file

@ -720,4 +720,5 @@
<string name="messages_in_cache_for_other_timelines">Messàgios in memòria temporànea pro àteras lìnias de tempus</string>
<string name="delete_cache_message">Ses seguru de bòlere isboidare sa memòria temporànea\? Si tenes abbotzos cun cuntenutos multimediales as a pèrdere sos cuntenutos alligados.</string>
<string name="clear_cache">Isbòida sa memòria temporànea</string>
<string name="default_system_language">Imprea sa limba predefinida de sistema</string>
</resources>

View file

@ -726,4 +726,5 @@
<string name="clear_cache">Önbelleği temizle</string>
<string name="messages_in_cache_for_other_timelines">Diğer zaman çizelgeleri için önbellekteki mesajlar</string>
<string name="messages_stored_in_drafts">Taslaklarda saklanan mesajlar</string>
<string name="default_system_language">Öntanımlı sistem dilini kullan</string>
</resources>

View file

@ -16,7 +16,7 @@
<string name="download_from" formatted="false">Tập tin: %1$s</string>
<string name="password">Mật khẩu</string>
<string name="email">Email</string>
<string name="accounts">Tài khoản</string>
<string name="accounts">Người</string>
<string name="toots">Tút</string>
<string name="tags">Tag</string>
<string name="save">Lưu</string>
@ -63,7 +63,7 @@
<string name="no_emoji">Ứng dụng không thể thu thập emoji tùy chỉnh vào thời điểm này.</string>
<string name="logout_account_confirmation">Bạn có chắc muốn đăng xuất @%1$s@%2$s\?</string>
<!-- Status -->
<string name="no_status">Không có tút nào</string>
<string name="no_status">Chưa có</string>
<string name="favourite_add">Thêm tút này vào mục yêu thích của bạn\?</string>
<string name="favourite_remove">Xóa tút này khỏi mục yêu thích của bạn\?</string>
<string name="reblog_add">Đăng lại tút này\?</string>
@ -75,7 +75,7 @@
<string name="more_action_5">Sao chép</string>
<string name="more_action_6">Chia sẻ</string>
<string name="more_action_7">Nhắc đến</string>
<string name="more_action_8">Thời gian ẩn</string>
<string name="more_action_8">Ẩn có thời hạn</string>
<string name="more_action_9">Xóa &amp; viết lại</string>
<string-array name="more_action_confirm">
<item>Ẩn tài khoản này?</item>
@ -143,11 +143,14 @@
<string name="about_thekinrar">Tìm máy chủ:</string>
<!-- Conversation -->
<!-- Accounts -->
<string name="no_accounts">Không có tài khoản nào</string>
<string name="no_accounts">Không có người nào</string>
<string name="no_follow_request">Không có yêu cầu theo dõi</string>
<string name="status_cnt">%1$s tút</string>
<string name="following_cnt">%1$s Đang theo dõi</string>
<string name="followers_cnt">%1$s Người theo dõi</string>
<string name="status_cnt">Tút
\n %1$s</string>
<string name="following_cnt">Đang theo dõi
\n %1$s</string>
<string name="followers_cnt">Người theo dõi
\n %1$s</string>
<string name="reject">Từ chối</string>
<!-- Scheduled toots -->
<string name="no_scheduled_toots">Không có tút đã lên lịch!</string>
@ -198,10 +201,10 @@
<string name="set_notif_follow_share">Khi ai đó đăng lại tút của tôi</string>
<string name="set_notif_follow_add">Khi ai đó thích tút của tôi</string>
<string name="set_notif_follow_mention">Khi ai đó nhắc tới tôi</string>
<string name="set_notif_follow_poll">Nhắc khi một cuộc bình chọn kết thúc</string>
<string name="set_notif_status">Nhắc khi có tút mới</string>
<string name="set_share_validation">Hiện xác nhận trước khi đăng lại tút</string>
<string name="set_share_validation_fav">Hiện xác nhận trước khi thích tút</string>
<string name="set_notif_follow_poll">Khi cuộc bình chọn kết thúc</string>
<string name="set_notif_status">Khi có tút mới</string>
<string name="set_share_validation">Hiện xác nhận trước khi đăng lại</string>
<string name="set_share_validation_fav">Hiện xác nhận trước khi thích</string>
<string name="set_notify">Thông báo?</string>
<string name="set_notif_silent">Tắt thông báo</string>
<string name="set_nsfw_timeout">Thời gian chờ của NSFW (giây, 0 = tắt)</string>
@ -237,7 +240,7 @@
<string name="action_unmute">Bỏ ẩn</string>
<string name="request_sent">Đã gửi yêu cầu</string>
<string name="followed_by">Theo dõi bạn</string>
<string name="set_capitalize">Viết hoa chữ đầu khi trả lời</string>
<string name="set_capitalize">Tự động xuống dòng khi trả lời</string>
<string name="set_resize_picture">Giảm kích cỡ hình ảnh</string>
<string name="set_resize_video">Giảm kích thước video</string>
<!-- Quick settings for notifications -->
@ -273,7 +276,7 @@
<string name="action_lists_add_to">Thêm vào danh sách</string>
<string name="action_lists_delete">Xoá danh sách</string>
<string name="action_lists_title_placeholder">Tên danh sách mới</string>
<string name="action_lists_add_user">Đã thêm tài khoản vào danh sách!</string>
<string name="action_lists_add_user">Đã thêm người này vào danh sách!</string>
<string name="action_lists_empty">Bạn chưa có danh sách nào!</string>
<!-- Migration -->
<string name="account_moved_to">%1$s đã chuyển sang %2$s</string>
@ -325,7 +328,7 @@
<string name="channel_notif_status">Tút mới</string>
<string name="channel_notif_media">Tải media</string>
<string name="select_sound">Chọn âm thanh</string>
<string name="set_enable_time_slot">Hiện thời gian đăng</string>
<string name="set_enable_time_slot">Thời gian thông báo</string>
<string name="block_domain_confirm_message">Bạn có chắc bỏ chặn %s\?
\n
\nBạn sẽ không thấy bất kỳ nội dung nào từ máy chủ này trong bảng tin và thông báo của bạn. Những người theo dõi bạn từ máy chủ này cũng sẽ bị xóa.</string>
@ -405,9 +408,9 @@
<string name="poll_choice_s">Lựa chọn %d</string>
<string name="poll_invalid_choices">Bạn cần cho ít nhất 2 lựa chọn!</string>
<string name="done">Xong</string>
<string name="poll_finish_at">kết thúc lúc %s</string>
<string name="poll_finish_at">kết thúc %s</string>
<string name="vote">Bình chọn</string>
<string name="notif_poll">Cuộc bình chọn bạn tham gia đã kết thúc</string>
<string name="notif_poll">Cuộc bình chọn bạn đã tham gia kết thúc</string>
<string name="notif_poll_self">Cuộc bình chọn của bạn đã kết thúc</string>
<string name="settings_category_notif_categories">Loại</string>
<string name="move_timeline">Chuyển bảng tin</string>
@ -490,7 +493,7 @@
<string name="voice_message">Ghi âm</string>
<string name="set_enable_time_slot_indication">Ứng dụng sẽ gửi thông báo vào thời điểm chỉ định. Bạn có thể đảo ngược (tức là: im lặng) thời điểm này bằng con quay bên phải.</string>
<string name="set_fit_preview_indication">Không cắt ảnh xem trước</string>
<string name="set_capitalize_indication">Tự động chèn dấu ngắt dòng sau phần đề cập để viết hoa chữ cái đầu tiên</string>
<string name="set_capitalize_indication">Tự động xuống dòng và viết hoa chữ đầu tiên sau khi nhắc đến ai đó.</string>
<string name="settings_title_custom_sharing_indication">Chia sẻ tút với nguồn cấp dữ liệu RSS</string>
<string name="compose">Soạn thảo</string>
<string name="select">Chọn</string>
@ -502,8 +505,8 @@
\n
\nBạn có thể thêm nội dung. Cảm ơn bạn!</string>
<string name="visibility">Mức độ hiển thị</string>
<string name="set_disable_animated_emoji">Tắt emoji dạng GIF</string>
<string name="report_account">Báo cáo tài khoản</string>
<string name="set_disable_animated_emoji">Tắt emoji GIF</string>
<string name="report_account">Báo cáo người này</string>
<plurals name="number_of_voters">
<item quantity="other">%d bình chọn</item>
</plurals>
@ -527,10 +530,10 @@
<string name="poll_duplicated_entry">Có lựa chọn trùng lặp!</string>
<string name="set_clear_cache_exit">Xóa bộ nhớ đệm khi thoát</string>
<string name="set_clear_cache_exit_indication">Bộ nhớ đệm (media, tút, dữ liệu từ trình duyệt tích hợp) sẽ tự động bị xóa khi thoát ứng dụng.</string>
<string name="unfollow_confirm">Bạn có muốn ngưng theo dõi tài khoản này\?</string>
<string name="set_unfollow_validation">Yêu cầu xác nhận trước khi hủy theo dõi ai đó</string>
<string name="unfollow_confirm">Bạn có muốn ngưng theo dõi người này\?</string>
<string name="set_unfollow_validation">Yêu cầu xác nhận trước khi ngưng theo dõi ai đó.</string>
<string name="replace_medium">Thay thế liên kết từ Medium</string>
<string name="replace_medium_description">Thay thế liên kết từ medium.com với một giao diện mã nguồn mở, chú trọng bảo mật.</string>
<string name="replace_medium_description">Dùng một frontend thay thế cho Medium</string>
<string name="replace_medium_host">Mặc định: scribe.rip</string>
<string name="set_push_notifications">Sử dụng hệ thống thông báo đẩy để nhận thông báo trong thời gian thực.</string>
<string name="action_add_notes">Thêm ghi chú</string>
@ -615,10 +618,10 @@
<string name="not_valid_list_name">Tên danh sách không hợp lệ!</string>
<string name="no_account_in_list">Không có người dùng nào trong danh sách này!</string>
<string name="scheduled">Đã lên lịch</string>
<string name="notifications_are">Trong khoảng thời gian</string>
<string name="notifications_are">Trong khoảng thời gian này</string>
<string name="pref_theme_base">Theme gốc</string>
<string name="pref_theme_base_summary">Chọn theme gốc là tối hay sáng</string>
<string name="customize_timelines">Tùy chỉnh bảng tin</string>
<string name="customize_timelines">Tùy chỉnh</string>
<string name="pref_contributor">Theme của cộng đồng</string>
<string name="pref_contributor_summary">Chọn một theme được tạo bởi cộng đồng</string>
<string name="display">Hiển thị</string>
@ -644,7 +647,7 @@
<string name="toots_visibility_title">Kiểu tút mặc định:</string>
<string name="toast_bookmark">Đã thêm tút vào mục yêu thích!</string>
<string name="toast_unbookmark">Đã xóa tút khỏi mục yêu thích!</string>
<string name="set_accounts_page">Số lượng tài khoản mỗi lần tải</string>
<string name="set_accounts_page">Số lượng người dùng mỗi lần tải</string>
<string name="set_notifications_page">Số lượng thông báo mỗi lần tải</string>
<string name="category_music">Âm nhạc</string>
<string name="cannot_be_empty">Không thể để trống mục này!</string>
@ -665,7 +668,7 @@
<string name="instance_not_valid">Địa chỉ máy chủ không hợp lệ!</string>
<string name="boosted_by">Đăng lại bởi</string>
<string name="favourited_by">Thích bởi</string>
<string name="followers_only">Hạn chế</string>
<string name="followers_only">Riêng tư</string>
<string name="other">Khác</string>
<string name="eg_sensitive_content">Vd: Nội Dung Nhạy Cảm</string>
<string name="add_status">Thêm trạng thái</string>
@ -688,11 +691,11 @@
<string name="report_val2">Đây là spam</string>
<string name="report_val_more2">Liên kết độc hại, giả tương tác hoặc trả lời lặp đi lặp lại</string>
<string name="report_val3">Vi phạm quy tắc máy chủ</string>
<string name="report_1_unfollow">Bạn đang theo dõi tài khoản này. Để không nhìn thấy tút của họ trong bảng tin nữa, hãy ngưng theo dõi họ.</string>
<string name="report_1_unfollow">Bạn đang theo dõi người này. Để không nhìn thấy tút của họ trong bảng tin nữa, hãy ngưng theo dõi họ.</string>
<string name="report_1_mute_title">Ẩn %1$s</string>
<string name="report_1_mute">Bạn sẽ không thấy tút của họ. Họ vẫn có thể theo dõi bạn và thấy tút của bạn nhưng không biết rằng họ bị ẩn.</string>
<string name="report_1_block_title">Chặn %1$s</string>
<string name="report_more_remote">Tài khoản này từ một máy chủ khác. Gửi luôn cho kiểm duyệt viên máy chủ đó\?</string>
<string name="report_more_remote">Người này từ một máy chủ khác. Gửi luôn cho kiểm duyệt viên máy chủ đó\?</string>
<string name="report_more_forward">Chuyển tiếp %1$s</string>
<string name="report_sent">Đã gửi báo cáo!</string>
<string name="report_1_block">Bạn sẽ không thấy tút của họ. Họ sẽ không thể xem tút của bạn hoặc theo dõi bạn. Họ sẽ biếtrằng họ bị chặn.</string>
@ -704,7 +707,7 @@
<string name="notif_display_poll_results">Kết quả bình chọn</string>
<string name="notif_display_updates_from_people">Cập nhật từ mọi người</string>
<string name="notif_display_follows">Theo dõi</string>
<string name="mark_all_as_read">Đánh dấu tất cả là đã đọc</string>
<string name="mark_all_as_read">Đánh dấu đã đọc xong</string>
<string name="display_all_categories">Hiện toàn bộ</string>
<string name="delete_notification_all_warning">Bạn có chắc muốn xóa tất cả thông báo\? Không thể khôi phục lại.</string>
<string name="about_mastodon">\"Mastodon không phải là một trang web duy nhất như Twitter hoặc Facebook, đó là một mạng lưới hàng ngàn cộng đồng được điều hành bởi các tổ chức và cá nhân khác nhau cung cấp trải nghiệm truyền thông xã hội liền mạch.\"</string>
@ -730,7 +733,7 @@
<string name="most_recent">Gần đây nhất</string>
<string name="filter">Bộ lọc</string>
<string name="domain">Tên miền</string>
<string name="origin_report">Nguồn gốc tài khoản bị báo cáo</string>
<string name="origin_report">Máy chủ người bị báo cáo</string>
<string name="status">Trạng thái</string>
<string name="resolved">Đã xử lý</string>
<string name="approved">Đã duyệt</string>
@ -745,10 +748,11 @@
<string name="label_line">Đường thẳng</string>
<string name="label_eraser_mode">Chế độ Xóa</string>
<string name="delete_cache">Xóa cache</string>
<string name="messages_in_cache_for_home">Tút trong cache cho Bảng Tin</string>
<string name="messages_stored_in_drafts">Tút lưu trong nháp</string>
<string name="delete_cache_message">Bạn có chắc muốn xóa cache\? Nếu bạn có tút nháp đính kèm media, nó sẽ bị mất.</string>
<string name="messages_in_cache_for_other_timelines">Tút trong cache những Bảng Tin khác</string>
<string name="files_cache_size">Kích cỡ cache</string>
<string name="clear_cache">Xóa cache</string>
<string name="messages_in_cache_for_home">Bộ nhớ đệm tút Bảng Tin</string>
<string name="messages_stored_in_drafts">Tút nháp</string>
<string name="delete_cache_message">Bạn có chắc muốn xóa bộ nhớ đệm\? Nếu bạn có tút nháp đính kèm media, nó sẽ bị mất.</string>
<string name="messages_in_cache_for_other_timelines">Bộ nhớ đệm tút những Bảng Tin khác</string>
<string name="files_cache_size">Dung lượng cho phép</string>
<string name="clear_cache">Xóa bộ nhớ đệm</string>
<string name="default_system_language">Dùng ngôn ngữ hệ thống</string>
</resources>

View file

@ -779,6 +779,7 @@
<string name="SET_FEATURED_TAG_ACTION" translatable="false">SET_FEATURED_TAG_ACTION</string>
<string name="SET_RETRIEVE_METADATA_IF_URL_FROM_EXTERAL" translatable="false">SET_RETRIEVE_METADATA_IF_URL_FROM_EXTERAL</string>
<string name="SET_TRANSLATE_VALUES_RESET" translatable="false">SET_TRANSLATE_VALUES_RESET</string>
<string-array name="SET_TRANSLATE_ENTRIES" translatable="false">
<item>en</item>
<item>fr</item>
@ -863,6 +864,7 @@
<string name="SET_TIME_FROM" translatable="false">SET_TIME_FROM</string>
<string name="SET_TIME_TO" translatable="false">SET_TIME_TO</string>
<string name="SET_MED_DESC_TIMEOUT" translatable="false">SET_MED_DESC_TIMEOUT</string>
<string name="SET_COMPOSE_LANGUAGE" translatable="false">SET_COMPOSE_LANGUAGE</string>
<string name="SET_NOTIF_FOLLOW" translatable="false">SET_NOTIF_FOLLOW</string>
@ -894,7 +896,7 @@
<string name="SET_ACCOUNTS_PER_CALL" translatable="false">SET_ACCOUNTS_PER_CALL</string>
<string name="SET_STATUSES_PER_CALL" translatable="false">SET_STATUSES_PER_CALL</string>
<string name="SET_NOTIFICATIONS_PER_CALL" translatable="false">SET_NOTIFICATIONS_PER_CALL</string>
<string name="INSTANCE_INFO" translatable="false">INSTANCE_INFO</string>
<string name="SET_INVIDIOUS" translatable="false">SET_INVIDIOUS</string>
<string name="SET_INVIDIOUS_HOST" translatable="false">SET_INVIDIOUS_HOST</string>
<string name="DEFAULT_INVIDIOUS_HOST" translatable="false">invidious.snopyta.org</string>
@ -978,6 +980,8 @@
<string name="files_cache_size">File cache size</string>
<string name="clear_cache">Clear cache</string>
<string name="delete_cache_message">Are you sure you want to delete cache? If you have drafts with media, the attached media will be lost.</string>
<string name="default_system_language">Use the default system language</string>
<string name="message_language">Language for messages</string>
<string-array name="photo_editor_emoji" translatable="false">
<!-- Smiles -->

View file

@ -187,7 +187,7 @@
<item name="iconTint">@color/cyanea_accent_dark_reference</item>
<item name="strokeColor">@color/cyanea_accent_dark_reference</item>
<item name="rippleColor">@color/cyanea_accent_dark_reference</item>
<item name="android:backgroundTint">@color/cyanea_accent_dark_reference</item>
<item name="backgroundTint">@color/cyanea_accent_dark_reference</item>
</style>
<style name="MyOutlinedButton" parent="Widget.MaterialComponents.Button.OutlinedButton">

View file

@ -11,4 +11,10 @@
app:summary="@string/set_push_notifications"
app:title="@string/set_change_locale"
app:useSimpleSummaryProvider="true" />
<Preference
android:key="@string/SET_TRANSLATE_VALUES_RESET"
android:summary="@string/default_system_language"
android:title="@string/reset"
app:iconSpaceReserved="false" />
</PreferenceScreen>

View file

@ -0,0 +1 @@
- Fix some bugs reported.

View file

@ -0,0 +1,6 @@
- Keep improving the scroll behaviour
- Scroll to top (tab reselection) will fetch new messages and then scroll to top
- Remove focus point for fit media preview
- Fix cannot share with one account
- Fix black theme
- Fix some button colors

View file

@ -0,0 +1,16 @@
Added:
- Set compose language (from compose menu -> three vertical dots)
- Add reactions support for Pleroma
- Add privacy indicator at the top right
Changed
- Improve the scrolling behaviour
- Scroll to top (tab reselection) will fetch new messages and then scroll to top
Fixed:
- Empty tag timelines
- Remove focus point for fit media preview
- Fix cannot share with one account
- Fix black theme
- Theme cannot be selected
- Fix some button colors