Compare commits

...

37 commits

Author SHA1 Message Date
a06fb1657d
Add support for Nyastodon-style emoji reactions 2023-02-08 21:13:16 +09:00
Thomas
97ba87aba7 Release 3.17.0 2023-02-05 18:59:05 +01:00
Thomas
7603db8ce0 Missing media description for previews 2023-02-05 18:53:05 +01:00
Thomas
663e33466d Merge remote-tracking branch 'origin/develop' into develop 2023-02-05 16:50:54 +01:00
Thomas
cbed3f1ae1 Fix media cannot be downloaded or shared (Android 10) 2023-02-05 16:50:08 +01:00
Poesty Li
8bdaf8d410
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1077 of 1077 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-05 11:55:29 +01:00
Thomas
6595af849e Fix forward tags in replies 2023-02-05 11:55:19 +01:00
Thomas
4111d00025 Group mentions at the top 2023-02-05 11:00:34 +01:00
Thomas
f75d8258f4 Fix button sizes not updated 2023-02-04 17:53:25 +01:00
Thomas
7a11e156a5 Merge remote-tracking branch 'origin/develop' into develop 2023-02-04 11:23:56 +01:00
Thomas
8977990fea Cache messages 2023-02-04 11:23:31 +01:00
Poesty Li
bc44a9be15
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1077 of 1077 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-03 18:36:46 +01:00
Eduardo
88d9bf4629
Translated using Weblate (Portuguese)
Currently translated at 89.8% (968 of 1077 strings)

Co-authored-by: Eduardo <edu200399lim@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pt/
Translation: Fedilab/Strings
2023-02-03 18:36:45 +01:00
josé m
65706e727c
Translated using Weblate (Galician)
Currently translated at 100.0% (1077 of 1077 strings)

Co-authored-by: josé m <correoxm@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/
Translation: Fedilab/Strings
2023-02-03 18:36:45 +01:00
Thomas
b64fd393e9 Fix worker 2023-02-03 17:42:50 +01:00
Thomas
2b300ceae4 record cache work 2023-02-03 17:23:33 +01:00
Thomas
6e4bb95dda More deep link detection 2023-02-02 18:03:09 +01:00
Thomas
440ad039be Fix issues 2023-02-02 17:44:16 +01:00
Thomas
4c89a855c6 Some fixes 2023-02-02 14:18:52 +01:00
Thomas
0ca53b75d2 Fix crashes when replying 2023-02-02 14:03:13 +01:00
Oğuz Ersen
91eb579e2c
Translated using Weblate (Turkish)
Currently translated at 100.0% (1077 of 1077 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-02-02 11:40:27 +01:00
Ajeje Brazorf
1aad4f07df
Translated using Weblate (Sardinian)
Currently translated at 99.2% (1069 of 1077 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2023-02-02 11:40:26 +01:00
claleb
335842c8a2
Translated using Weblate (German)
Currently translated at 100.0% (1077 of 1077 strings)

Co-authored-by: claleb <weblate@claleb.de>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/
Translation: Fedilab/Strings
2023-02-02 11:40:26 +01:00
Lukáš Jelínek
c10d078add
Translated using Weblate (Czech)
Currently translated at 99.9% (1076 of 1077 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-02-02 11:40:26 +01:00
Poesty Li
67cdceb90e
Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (1075 of 1075 strings)

Co-authored-by: Poesty Li <poesty7450@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/zh_Hans/
Translation: Fedilab/Strings
2023-02-01 19:01:52 +01:00
Thomas
ddbd3f8684 Release 3.16.4 2023-02-01 19:00:38 +01:00
Thomas
4ae8011eff Auto fetch messages 2023-02-01 18:52:55 +01:00
Thomas
d61dbb0315 Auto fetch messages 2023-02-01 17:56:20 +01:00
Thomas
3c13bf7199 Merge remote-tracking branch 'origin/develop' into develop 2023-02-01 15:16:42 +01:00
Thomas
b19cd7c0c9 Add settings 2023-02-01 15:14:37 +01:00
Dan
17438f6f5c
Translated using Weblate (Ukrainian)
Currently translated at 62.6% (668 of 1067 strings)

Co-authored-by: Dan <denqwerta@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/uk/
Translation: Fedilab/Strings
2023-02-01 13:54:31 +01:00
Oğuz Ersen
f05ab805ff
Translated using Weblate (Turkish)
Currently translated at 100.0% (1067 of 1067 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/
Translation: Fedilab/Strings
2023-02-01 13:54:31 +01:00
Lukáš Jelínek
212cd9d54e
Translated using Weblate (Czech)
Currently translated at 99.9% (1066 of 1067 strings)

Co-authored-by: Lukáš Jelínek <devel@aiken.cz>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/
Translation: Fedilab/Strings
2023-02-01 13:54:31 +01:00
Dan
f4437b6955
Translated using Weblate (Ukrainian)
Currently translated at 15.4% (13 of 84 strings)

Co-authored-by: Dan <denqwerta@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2023-02-01 13:54:07 +01:00
0xd9a
0e16cb1730 update schedule dialog 2023-02-01 06:41:33 +05:30
Dan
67cfa9cf01
Translated using Weblate (Ukrainian)
Currently translated at 11.9% (10 of 84 strings)

Co-authored-by: Dan <denqwerta@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/fedilab/description/uk/
Translation: Fedilab/description
2023-02-01 00:55:43 +01:00
Ajeje Brazorf
7bdf2aa1ad
Translated using Weblate (Sardinian)
Currently translated at 99.2% (1059 of 1067 strings)

Translated using Weblate (Sardinian)

Currently translated at 99.2% (1058 of 1066 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/
Translation: Fedilab/Strings
2023-01-31 18:36:50 +01:00
63 changed files with 934 additions and 289 deletions

View file

@ -13,8 +13,8 @@ android {
defaultConfig { defaultConfig {
minSdk 21 minSdk 21
targetSdk 33 targetSdk 33
versionCode 475 versionCode 477
versionName "3.16.3" versionName "3.17.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
flavorDimensions "default" flavorDimensions "default"

View file

@ -1,4 +1,14 @@
[ [
{
"version": "3.17.0",
"code": "477",
"note": "Added:\n- Peertube 2FA support\n- Cache home in background (default disabled -> New settings category and per account) / change frequency\n- Auto-fetch missing messages for the Home (default disabled -> in Settings - Timelines)\n- Automatically switch between tabs when searching\n- More deep links detection\n- Allow to group mentions at the top (default: disabled)\n\n\nFixed:\n- Dynamic color for Android 12+\n- Missing media description for previews\n- Fix a crash when replying\n- Fix button size not changed\n- Forward tags in replies\n- Media cannot be downloaded or shared with Android 10\n- Some crashes"
},
{
"version": "3.16.4",
"code": "476",
"note": "Added:\n- Cache home in background (default disabled -> New settings category and per account) / change frequency\n- Auto-fetch missing messages for the Home (default disabled -> in Settings - Timelines)\n- Automatically switch between tabs when searching\n\nFixed:\n- Some crashes"
},
{ {
"version": "3.16.3", "version": "3.16.3",
"code": "475", "code": "475",

View file

@ -1811,9 +1811,9 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
* Allow to scroll to top for bottom navigation items * Allow to scroll to top for bottom navigation items
*/ */
private void scrollToTop() { private void scrollToTop() {
int position = binding.tabLayout.getSelectedTabPosition();
if (binding.viewPager.getAdapter() != null) { if (binding.viewPager.getAdapter() != null) {
Fragment fragment = (Fragment) binding.viewPager.getAdapter().instantiateItem(binding.viewPager, binding.tabLayout.getSelectedTabPosition()); Fragment fragment = (Fragment) binding.viewPager.getAdapter().instantiateItem(binding.viewPager, Math.max(position, 0));
if (fragment instanceof FragmentMastodonTimeline) { if (fragment instanceof FragmentMastodonTimeline) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop(); fragmentMastodonTimeline.scrollToTop();

View file

@ -233,6 +233,10 @@ public class HashTagActivity extends BaseActivity {
tagTimeline.any.add(stripTag.trim()); tagTimeline.any.add(stripTag.trim());
pinnedTimeline.tagTimeline = tagTimeline; pinnedTimeline.tagTimeline = tagTimeline;
pinned.pinnedTimelines.add(pinnedTimeline); pinned.pinnedTimelines.add(pinnedTimeline);
if (pinned.instance == null || pinned.user_id == null) {
pinned.instance = MainActivity.currentInstance;
pinned.user_id = MainActivity.currentUserID;
}
if (update) { if (update) {
new Pinned(HashTagActivity.this).updatePinned(pinned); new Pinned(HashTagActivity.this).updatePinned(pinned);
} else { } else {

View file

@ -250,7 +250,7 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
int position = binding.mediaViewpager.getCurrentItem(); int position = binding.mediaViewpager.getCurrentItem();
Attachment attachment = attachments.get(position); Attachment attachment = attachments.get(position);
if (Build.VERSION.SDK_INT >= 23) { if (Build.VERSION.SDK_INT >= 23) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MediaActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MediaActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE); ActivityCompat.requestPermissions(MediaActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE);
} else { } else {

View file

@ -836,6 +836,10 @@ public class ProfileActivity extends BaseActivity {
pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE; pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE;
pinnedTimeline.position = pinned.pinnedTimelines.size(); pinnedTimeline.position = pinned.pinnedTimelines.size();
pinned.pinnedTimelines.add(pinnedTimeline); pinned.pinnedTimelines.add(pinnedTimeline);
if (pinned.instance == null || pinned.user_id == null) {
pinned.instance = MainActivity.currentInstance;
pinned.user_id = MainActivity.currentUserID;
}
Pinned finalPinned = pinned; Pinned finalPinned = pinned;
boolean finalPresent = present; boolean finalPresent = present;
new Thread(() -> { new Thread(() -> {

View file

@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityReorderTabsBinding; import app.fedilab.android.databinding.ActivityReorderTabsBinding;
import app.fedilab.android.databinding.PopupSearchInstanceBinding; import app.fedilab.android.databinding.PopupSearchInstanceBinding;
import app.fedilab.android.mastodon.client.entities.app.BottomMenu; import app.fedilab.android.mastodon.client.entities.app.BottomMenu;
@ -261,7 +262,10 @@ public class ReorderTimelinesActivity extends BaseBarActivity implements OnStart
pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE; pinnedTimeline.type = Timeline.TimeLineEnum.REMOTE;
pinnedTimeline.position = pinned.pinnedTimelines.size(); pinnedTimeline.position = pinned.pinnedTimelines.size();
pinned.pinnedTimelines.add(pinnedTimeline); pinned.pinnedTimelines.add(pinnedTimeline);
if (pinned.user_id == null || pinned.instance == null) {
pinned.user_id = MainActivity.currentUserID;
pinned.instance = MainActivity.currentInstance;
}
if (update) { if (update) {
try { try {
new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned); new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned);

View file

@ -318,4 +318,18 @@ public interface MastodonStatusesService {
@Header("Authorization") String token, @Header("Authorization") String token,
@Path("id") String id @Path("id") String id
); );
@POST("statuses/{id}/react/{name}")
Call<Void> addReaction(
@Header("Authorization") String app_token,
@Path("id") String id,
@Path("name") String name
);
@POST("statuses/{id}/unreact/{name}")
Call<Void> removeReaction(
@Header("Authorization") String app_token,
@Path("id") String id,
@Path("name") String name
);
} }

View file

@ -29,6 +29,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import app.fedilab.android.mastodon.helper.SpannableHelper; import app.fedilab.android.mastodon.helper.SpannableHelper;
import de.timfreiheit.mathjax.android.MathJaxView;
public class Status implements Serializable, Cloneable { public class Status implements Serializable, Cloneable {
@ -108,6 +109,8 @@ public class Status implements Serializable, Cloneable {
public boolean cached = false; public boolean cached = false;
@SerializedName("is_maths") @SerializedName("is_maths")
public Boolean isMaths; public Boolean isMaths;
@SerializedName("reactions")
public List<Reaction> reactions;
public Attachment art_attachment; public Attachment art_attachment;
public boolean isExpended = false; public boolean isExpended = false;
@ -128,6 +131,7 @@ public class Status implements Serializable, Cloneable {
public transient Spannable contentSpan; public transient Spannable contentSpan;
public transient Spannable contentSpoilerSpan; public transient Spannable contentSpoilerSpan;
public transient Spannable contentTranslateSpan; public transient Spannable contentTranslateSpan;
public transient MathJaxView mathJaxView;
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {

View file

@ -166,8 +166,8 @@ public class BottomMenu implements Serializable {
throw new DBException("db is null. Wrong initialization."); throw new DBException("db is null. Wrong initialization.");
} }
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(Sqlite.COL_INSTANCE, BaseMainActivity.currentInstance); values.put(Sqlite.COL_INSTANCE, bottomMenu.instance);
values.put(Sqlite.COL_USER_ID, BaseMainActivity.currentUserID); values.put(Sqlite.COL_USER_ID, bottomMenu.user_id);
values.put(Sqlite.COL_BOTTOM_MENU, menuItemListToStringStorage(bottomMenu.bottom_menu)); values.put(Sqlite.COL_BOTTOM_MENU, menuItemListToStringStorage(bottomMenu.bottom_menu));
//Inserts bottom //Inserts bottom
try { try {

View file

@ -28,7 +28,6 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.Sqlite;
@ -104,8 +103,8 @@ public class Pinned implements Serializable {
throw new DBException("db is null. Wrong initialization."); throw new DBException("db is null. Wrong initialization.");
} }
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(Sqlite.COL_INSTANCE, BaseMainActivity.currentInstance); values.put(Sqlite.COL_INSTANCE, pinned.instance);
values.put(Sqlite.COL_USER_ID, BaseMainActivity.currentUserID); values.put(Sqlite.COL_USER_ID, pinned.user_id);
values.put(Sqlite.COL_PINNED_TIMELINES, mastodonPinnedTimelinesToStringStorage(pinned.pinnedTimelines)); values.put(Sqlite.COL_PINNED_TIMELINES, mastodonPinnedTimelinesToStringStorage(pinned.pinnedTimelines));
values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date())); values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date()));
//Inserts pinned //Inserts pinned

View file

@ -277,6 +277,7 @@ public class PinnedTimelineHelper {
pinnedTimeline.position = pinnedAll.pinnedTimelines.size(); pinnedTimeline.position = pinnedAll.pinnedTimelines.size();
pinnedTimeline.mastodonList = mastodonList; pinnedTimeline.mastodonList = mastodonList;
pinnedAll.pinnedTimelines.add(pinnedTimeline); pinnedAll.pinnedTimelines.add(pinnedTimeline);
try { try {
boolean exist = new Pinned(activity).pinnedExist(pinnedAll); boolean exist = new Pinned(activity).pinnedExist(pinnedAll);
if (exist) { if (exist) {
@ -304,7 +305,7 @@ public class PinnedTimelineHelper {
//Small hack to hide first tabs (they represent the item of the bottom menu) //Small hack to hide first tabs (they represent the item of the bottom menu)
toRemove = itemToRemoveInBottomMenu(activity); toRemove = itemToRemoveInBottomMenu(activity);
for (int i = 0; i < (FedilabPageAdapter.BOTTOM_TIMELINE_COUNT - toRemove); i++) { for (int i = 0; i < (FedilabPageAdapter.BOTTOM_TIMELINE_COUNT - toRemove); i++) {
activityMainBinding.tabLayout.addTab(activityMainBinding.tabLayout.newTab()); activityMainBinding.tabLayout.addTab(activityMainBinding.tabLayout.newTab(), false);
((ViewGroup) activityMainBinding.tabLayout.getChildAt(0)).getChildAt(i).setVisibility(View.GONE); ((ViewGroup) activityMainBinding.tabLayout.getChildAt(0)).getChildAt(i).setVisibility(View.GONE);
} }
} }
@ -413,7 +414,7 @@ public class PinnedTimelineHelper {
//We be used to fetch position of tabs //We be used to fetch position of tabs
String slug = pinnedTimeline.type.getValue() + (ident != null ? "|" + ident : ""); String slug = pinnedTimeline.type.getValue() + (ident != null ? "|" + ident : "");
tab.setTag(slug); tab.setTag(slug);
activityMainBinding.tabLayout.addTab(tab); activityMainBinding.tabLayout.addTab(tab, false);
pinnedTimelineVisibleList.add(pinnedTimeline); pinnedTimelineVisibleList.add(pinnedTimeline);
} }
} }

View file

@ -514,6 +514,8 @@ public class SpannableHelper {
Matcher matcherLink = link.matcher(finalUrl); Matcher matcherLink = link.matcher(finalUrl);
Pattern linkLong = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)(/[0-9]+)?$"); Pattern linkLong = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)(/[0-9]+)?$");
Matcher matcherLinkLong = linkLong.matcher(finalUrl); Matcher matcherLinkLong = linkLong.matcher(finalUrl);
Pattern userWithoutAt = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(users/([\\w._-]*[0-9]*))/statuses/([0-9]+)");
Matcher matcherUserWithoutAt = userWithoutAt.matcher(finalUrl);
if (matcherLink.find() && !finalUrl.contains("medium.com")) { if (matcherLink.find() && !finalUrl.contains("medium.com")) {
if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() { CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@ -567,6 +569,38 @@ public class SpannableHelper {
public void federatedStatus(Status status) { public void federatedStatus(Status status) {
} }
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
}
} else if (matcherUserWithoutAt.find() && !finalUrl.contains("medium.com")) {
if (matcherUserWithoutAt.group(4) != null && Objects.requireNonNull(matcherUserWithoutAt.group(4)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
intent.putExtra(Helper.ARG_STATUS, status);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
@Override
public void federatedAccount(Account account) {
}
});
} else {//It's an account
CrossActionHelper.fetchRemoteAccount(context, currentAccount, matcherUserWithoutAt.group(3) + "@" + matcherUserWithoutAt.group(1), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override @Override
public void federatedAccount(Account account) { public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class); Intent intent = new Intent(context, ProfileActivity.class);

View file

@ -265,7 +265,7 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
.setClearViewsEnabled(true) .setClearViewsEnabled(true)
.setTransparencyEnabled(true) .setTransparencyEnabled(true)
.build(); .build();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(EditImageActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != if (ContextCompat.checkSelfPermission(EditImageActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) { PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(EditImageActivity.this, ActivityCompat.requestPermissions(EditImageActivity.this,

View file

@ -76,12 +76,13 @@ public class FetchHomeWorker extends Worker {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
} }
public static void setRepeatHome(Context context, BaseAccount account) { public static void setRepeatHome(Context context, BaseAccount account, Data inputData) {
WorkManager.getInstance(context).cancelAllWorkByTag(Helper.WORKER_REFRESH_HOME + account.user_id + account.instance); WorkManager.getInstance(context).cancelAllWorkByTag(Helper.WORKER_REFRESH_HOME + account.user_id + account.instance);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String value = prefs.getString(context.getString(R.string.SET_FETCH_HOME_DELAY_VALUE) + account.user_id + account.instance, "60"); String value = prefs.getString(context.getString(R.string.SET_FETCH_HOME_DELAY_VALUE) + account.user_id + account.instance, "60");
PeriodicWorkRequest notificationPeriodic = new PeriodicWorkRequest.Builder(NotificationsWorker.class, Long.parseLong(value), TimeUnit.MINUTES) PeriodicWorkRequest notificationPeriodic = new PeriodicWorkRequest.Builder(FetchHomeWorker.class, Long.parseLong(value), TimeUnit.MINUTES)
.addTag(Helper.WORKER_REFRESH_NOTIFICATION) .setInputData(inputData)
.addTag(Helper.WORKER_REFRESH_HOME + account.user_id + account.instance)
.build(); .build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork(Helper.WORKER_REFRESH_HOME + account.user_id + account.instance, ExistingPeriodicWorkPolicy.REPLACE, notificationPeriodic); WorkManager.getInstance(context).enqueueUniquePeriodicWork(Helper.WORKER_REFRESH_HOME + account.user_id + account.instance, ExistingPeriodicWorkPolicy.REPLACE, notificationPeriodic);
} }
@ -90,17 +91,13 @@ public class FetchHomeWorker extends Worker {
@Override @Override
public ListenableFuture<ForegroundInfo> getForegroundInfoAsync() { public ListenableFuture<ForegroundInfo> getForegroundInfoAsync() {
if (Build.VERSION.SDK_INT >= 26) { if (Build.VERSION.SDK_INT >= 26) {
String channelName = "Notifications"; String channelName = "Fetch Home";
String channelDescription = "Fetched notifications"; String channelDescription = "Fetch home messages";
NotificationChannel notifChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW); NotificationChannel fetchHomeChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
notifChannel.setDescription(channelDescription); fetchHomeChannel.setDescription(channelDescription);
notifChannel.setSound(null, null); fetchHomeChannel.setSound(null, null);
notifChannel.setShowBadge(false); fetchHomeChannel.setShowBadge(false);
notificationManager.createNotificationChannel(notifChannel); notificationManager.createNotificationChannel(fetchHomeChannel);
if (notificationManager.getNotificationChannel("notifications") != null) {
notificationManager.deleteNotificationChannel("notifications");
}
} }
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
notificationBuilder.setSmallIcon(R.drawable.ic_notification) notificationBuilder.setSmallIcon(R.drawable.ic_notification)
@ -115,20 +112,20 @@ public class FetchHomeWorker extends Worker {
@NonNull @NonNull
private ForegroundInfo createForegroundInfo() { private ForegroundInfo createForegroundInfo() {
if (Build.VERSION.SDK_INT >= 26) { if (Build.VERSION.SDK_INT >= 26) {
String channelName = "Notifications"; String channelName = "Fetch Home";
String channelDescription = "Fetched notifications"; String channelDescription = "Fetch home messages";
NotificationChannel notifChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW); NotificationChannel fetchHomeChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
notifChannel.setSound(null, null); fetchHomeChannel.setSound(null, null);
notifChannel.setShowBadge(false); fetchHomeChannel.setShowBadge(false);
notifChannel.setDescription(channelDescription); fetchHomeChannel.setDescription(channelDescription);
notificationManager.createNotificationChannel(notifChannel); notificationManager.createNotificationChannel(fetchHomeChannel);
} }
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
notificationBuilder.setSmallIcon(R.drawable.ic_notification) notificationBuilder.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.ic_launcher_foreground)) .setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.ic_launcher_foreground))
.setContentTitle(getApplicationContext().getString(R.string.notifications)) .setContentTitle(getApplicationContext().getString(R.string.fetch_home_messages))
.setContentText(getApplicationContext().getString(R.string.fetch_notifications)) .setContentText(getApplicationContext().getString(R.string.set_fetch_home))
.setDefaults(NotificationCompat.DEFAULT_ALL) .setDefaults(NotificationCompat.DEFAULT_ALL)
.setSilent(true) .setSilent(true)
.setPriority(Notification.PRIORITY_LOW); .setPriority(Notification.PRIORITY_LOW);
@ -138,10 +135,15 @@ public class FetchHomeWorker extends Worker {
@NonNull @NonNull
@Override @Override
public Result doWork() { public Result doWork() {
setForegroundAsync(createForegroundInfo()); setForegroundAsync(createForegroundInfo());
String instance = getInputData().getString(Helper.ARG_INSTANCE);
String userId = getInputData().getString(Helper.ARG_USER_ID);
try { try {
List<BaseAccount> accounts = new Account(getApplicationContext()).getCrossAccounts(); BaseAccount account = new Account(getApplicationContext()).getUniqAccount(userId, instance);
for (BaseAccount account : accounts) { if (account != null) {
try { try {
fetchHome(getApplicationContext(), account); fetchHome(getApplicationContext(), account);
} catch (IOException e) { } catch (IOException e) {
@ -168,7 +170,7 @@ public class FetchHomeWorker extends Worker {
String max_id = null; String max_id = null;
MastodonTimelinesService mastodonTimelinesService = init(account.instance); MastodonTimelinesService mastodonTimelinesService = init(account.instance);
while (canContinue && call < max_calls) { while (canContinue && call < max_calls) {
Call<List<Status>> homeCall = mastodonTimelinesService.getHome(account.token, account.instance, max_id, null, status_per_page, null); Call<List<Status>> homeCall = mastodonTimelinesService.getHome(account.token, max_id, null, null, status_per_page, null);
if (homeCall != null) { if (homeCall != null) {
Response<List<Status>> homeResponse = homeCall.execute(); Response<List<Status>> homeResponse = homeCall.execute();
if (homeResponse.isSuccessful()) { if (homeResponse.isSuccessful()) {
@ -183,12 +185,7 @@ public class FetchHomeWorker extends Worker {
statusCache.type = Timeline.TimeLineEnum.HOME; statusCache.type = Timeline.TimeLineEnum.HOME;
statusCache.status_id = status.id; statusCache.status_id = status.id;
try { try {
int inserted = statusCacheDAO.insertOrUpdate(statusCache, Timeline.TimeLineEnum.HOME.getValue()); statusCacheDAO.insertOrUpdate(statusCache, Timeline.TimeLineEnum.HOME.getValue());
//We reached already cached messages
if (inserted == 0) {
canContinue = false;
break;
}
} catch (DBException e) { } catch (DBException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -206,9 +203,14 @@ public class FetchHomeWorker extends Worker {
canContinue = false; canContinue = false;
} }
} }
//Pause between calls (1 second)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
call++; call++;
} }
} }
} }

View file

@ -28,6 +28,7 @@ import app.fedilab.android.databinding.DrawerCacheBinding;
import app.fedilab.android.mastodon.client.entities.app.CacheAccount; import app.fedilab.android.mastodon.client.entities.app.CacheAccount;
import app.fedilab.android.mastodon.helper.CacheHelper; import app.fedilab.android.mastodon.helper.CacheHelper;
import app.fedilab.android.mastodon.helper.MastodonHelper; import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.peertube.helper.Helper;
public class CacheAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class CacheAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@ -61,9 +62,17 @@ public class CacheAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
CacheAccount cacheAccount = accountList.get(position); CacheAccount cacheAccount = accountList.get(position);
AccountCacheViewHolder holder = (AccountCacheViewHolder) viewHolder; AccountCacheViewHolder holder = (AccountCacheViewHolder) viewHolder;
MastodonHelper.loadPPMastodon(holder.binding.pp, cacheAccount.account.mastodon_account);
holder.binding.acct.setText(String.format("@%s@%s", cacheAccount.account.mastodon_account.username, cacheAccount.account.instance)); if (cacheAccount.account.mastodon_account != null) {
holder.binding.displayName.setText(cacheAccount.account.mastodon_account.display_name); MastodonHelper.loadPPMastodon(holder.binding.pp, cacheAccount.account.mastodon_account);
holder.binding.acct.setText(String.format("@%s@%s", cacheAccount.account.mastodon_account.username, cacheAccount.account.instance));
holder.binding.displayName.setText(cacheAccount.account.mastodon_account.display_name);
} else if (cacheAccount.account.peertube_account != null) {
Helper.loadAvatar(context, cacheAccount.account.peertube_account, holder.binding.pp);
holder.binding.acct.setText(String.format("@%s@%s", cacheAccount.account.peertube_account.getUsername(), cacheAccount.account.instance));
holder.binding.displayName.setText(cacheAccount.account.peertube_account.getDisplayName());
}
CacheHelper.getTimelineValues(context, cacheAccount.account, countStatuses -> { CacheHelper.getTimelineValues(context, cacheAccount.account, countStatuses -> {
if (countStatuses != null && countStatuses.size() == 3) { if (countStatuses != null && countStatuses.size() == 3) {
holder.binding.homeCount.setText(String.valueOf(countStatuses.get(0))); holder.binding.homeCount.setText(String.valueOf(countStatuses.get(0)));

View file

@ -20,6 +20,7 @@ import static app.fedilab.android.BaseMainActivity.currentAccount;
import static app.fedilab.android.BaseMainActivity.emojis; import static app.fedilab.android.BaseMainActivity.emojis;
import static app.fedilab.android.BaseMainActivity.instanceInfo; import static app.fedilab.android.BaseMainActivity.instanceInfo;
import static app.fedilab.android.mastodon.activities.ComposeActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE; import static app.fedilab.android.mastodon.activities.ComposeActivity.MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE;
import static de.timfreiheit.mathjax.android.MathJaxConfig.Input.TeX;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -127,6 +128,8 @@ import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.imageeditor.EditImageActivity; import app.fedilab.android.mastodon.imageeditor.EditImageActivity;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.SearchVM; import app.fedilab.android.mastodon.viewmodel.mastodon.SearchVM;
import de.timfreiheit.mathjax.android.MathJaxConfig;
import de.timfreiheit.mathjax.android.MathJaxView;
import es.dmoral.toasty.Toasty; import es.dmoral.toasty.Toasty;
@ -311,8 +314,10 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
} }
//Put other accounts mentioned at the bottom //Put other accounts mentioned at the bottom
boolean capitalize = sharedpreferences.getBoolean(context.getString(R.string.SET_CAPITALIZE), true); boolean capitalize = sharedpreferences.getBoolean(context.getString(R.string.SET_CAPITALIZE), true);
boolean mentionsAtTop = sharedpreferences.getBoolean(context.getString(R.string.SET_MENTIONS_AT_TOP), false);
if (inReplyToUser != null) { if (inReplyToUser != null) {
if (capitalize) { if (capitalize && !mentionsAtTop) {
statusDraft.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + "\n" : "@" + inReplyToUser.acct + "\n"; statusDraft.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + "\n" : "@" + inReplyToUser.acct + "\n";
} else { } else {
statusDraft.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + " " : "@" + inReplyToUser.acct + " "; statusDraft.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + " " : "@" + inReplyToUser.acct + " ";
@ -321,7 +326,9 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
holder.binding.content.setText(statusDraft.text); holder.binding.content.setText(statusDraft.text);
statusDraft.cursorPosition = statusDraft.text.length(); statusDraft.cursorPosition = statusDraft.text.length();
if (statusDraft.mentions.size() > 1) { if (statusDraft.mentions.size() > 1) {
statusDraft.text += "\n"; if (!mentionsAtTop) {
statusDraft.text += "\n";
}
for (int i = 1; i < statusDraft.mentions.size(); i++) { for (int i = 1; i < statusDraft.mentions.size(); i++) {
String tootTemp = String.format("@%s ", statusDraft.mentions.get(i).acct); String tootTemp = String.format("@%s ", statusDraft.mentions.get(i).acct);
statusDraft.text = String.format("%s ", (statusDraft.text + tootTemp.trim())); statusDraft.text = String.format("%s ", (statusDraft.text + tootTemp.trim()));
@ -392,7 +399,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
* @param position - int position of the media in the message * @param position - int position of the media in the message
*/ */
private void pickupMedia(ComposeActivity.mediaType type, int position) { private void pickupMedia(ComposeActivity.mediaType type, int position) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) != if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED) { PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((Activity) context, ActivityCompat.requestPermissions((Activity) context,
@ -702,19 +709,26 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
} }
Matcher mathsPatterns = Helper.mathsComposePattern.matcher((s.toString())); Matcher mathsPatterns = Helper.mathsComposePattern.matcher((s.toString()));
if (mathsPatterns.find()) { if (mathsPatterns.find()) {
if (holder.binding.laTexViewContainer.getVisibility() != View.VISIBLE) { if (holder.binding.laTexViewContainer.getChildCount() == 0) {
holder.binding.laTexViewContainer.setVisibility(View.VISIBLE); MathJaxConfig mathJaxConfig = new MathJaxConfig();
mathJaxConfig.setAutomaticLinebreaks(true);
mathJaxConfig.setInput(TeX);
switch (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) { switch (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) {
case Configuration.UI_MODE_NIGHT_YES: case Configuration.UI_MODE_NIGHT_YES:
holder.binding.laTexView.setTextColor("white"); mathJaxConfig.setTextColor("white");
break; break;
case Configuration.UI_MODE_NIGHT_NO: case Configuration.UI_MODE_NIGHT_NO:
holder.binding.laTexView.setTextColor("black"); mathJaxConfig.setTextColor("black");
break; break;
} }
statusList.get(holder.getBindingAdapterPosition()).mathJaxView = new MathJaxView(context, mathJaxConfig);
holder.binding.laTexViewContainer.addView(statusList.get(holder.getBindingAdapterPosition()).mathJaxView);
holder.binding.laTexViewContainer.setVisibility(View.VISIBLE);
} }
holder.binding.laTexView.setInputText(s.toString()); if (statusList.get(holder.getBindingAdapterPosition()).mathJaxView != null) {
statusList.get(holder.getBindingAdapterPosition()).mathJaxView.setInputText(s.toString());
}
} else { } else {
holder.binding.laTexViewContainer.setVisibility(View.GONE); holder.binding.laTexViewContainer.setVisibility(View.GONE);
} }
@ -1297,6 +1311,8 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
ComposeViewHolder holder = (ComposeViewHolder) viewHolder; ComposeViewHolder holder = (ComposeViewHolder) viewHolder;
boolean extraFeatures = sharedpreferences.getBoolean(context.getString(R.string.SET_EXTAND_EXTRA_FEATURES) + MainActivity.currentUserID + MainActivity.currentInstance, false); boolean extraFeatures = sharedpreferences.getBoolean(context.getString(R.string.SET_EXTAND_EXTRA_FEATURES) + MainActivity.currentUserID + MainActivity.currentInstance, false);
boolean mathsComposer = sharedpreferences.getBoolean(context.getString(R.string.SET_MATHS_COMPOSER), true); boolean mathsComposer = sharedpreferences.getBoolean(context.getString(R.string.SET_MATHS_COMPOSER), true);
boolean forwardTag = sharedpreferences.getBoolean(context.getString(R.string.SET_FORWARD_TAGS_IN_REPLY), true);
if (mathsComposer) { if (mathsComposer) {
holder.binding.buttonMathsComposer.setVisibility(View.VISIBLE); holder.binding.buttonMathsComposer.setVisibility(View.VISIBLE);
@ -1565,13 +1581,48 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
currentCursorPosition = holder.getLayoutPosition(); currentCursorPosition = holder.getLayoutPosition();
} }
}); });
boolean capitalize = sharedpreferences.getBoolean(context.getString(R.string.SET_CAPITALIZE), true);
boolean mentionsAtTop = sharedpreferences.getBoolean(context.getString(R.string.SET_MENTIONS_AT_TOP), false);
if (statusDraft.cursorPosition <= holder.binding.content.length()) { if (statusDraft.cursorPosition <= holder.binding.content.length()) {
holder.binding.content.setSelection(statusDraft.cursorPosition); if (!mentionsAtTop) {
holder.binding.content.setSelection(statusDraft.cursorPosition);
} else {
if (capitalize && !statusDraft.text.endsWith("\n")) {
statusDraft.text += "\n";
holder.binding.content.setText(statusDraft.text);
}
holder.binding.content.setSelection(holder.binding.content.getText().length());
}
} }
if (statusDraft.setCursorToEnd) { if (statusDraft.setCursorToEnd) {
statusDraft.setCursorToEnd = false; statusDraft.setCursorToEnd = false;
holder.binding.content.setSelection(holder.binding.content.getText().length()); holder.binding.content.setSelection(holder.binding.content.getText().length());
} }
if (forwardTag && position > 0 && statusDraft.text != null && !statusDraft.text.contains("#")) {
Status status = statusList.get(position - 1).reblog == null ? statusList.get(position - 1) : statusList.get(position - 1).reblog;
if (status.tags != null && status.tags.size() > 0) {
statusDraft.text += "\n\n";
int lenght = 0;
for (Tag tag : status.tags) {
statusDraft.text += "#" + tag.name + " ";
lenght += ("#" + tag.name + " ").length();
}
holder.binding.content.setText(statusDraft.text);
statusDraft.cursorPosition = statusDraft.text.length() - lenght - 3;
statusDraft.setCursorToEnd = false;
holder.binding.content.setSelection(statusDraft.text.length() - lenght - 3);
}
} else if (forwardTag && position > 0 && statusDraft.text != null && statusDraft.text.contains("#")) {
Status status = statusList.get(position - 1).reblog == null ? statusList.get(position - 1) : statusList.get(position - 1).reblog;
int lenght = 0;
for (Tag tag : status.tags) {
lenght += ("#" + tag.name + " ").length();
}
statusDraft.cursorPosition = statusDraft.text.length() - lenght - 3;
statusDraft.setCursorToEnd = false;
holder.binding.content.setSelection(statusDraft.text.length() - lenght - 3);
}
if (statusDraft.spoiler_text != null) { if (statusDraft.spoiler_text != null) {
holder.binding.contentSpoiler.setText(statusDraft.spoiler_text); holder.binding.contentSpoiler.setText(statusDraft.spoiler_text);
holder.binding.contentSpoiler.setSelection(holder.binding.contentSpoiler.getText().length()); holder.binding.contentSpoiler.setSelection(holder.binding.contentSpoiler.getText().length());

View file

@ -34,6 +34,7 @@ import app.fedilab.android.mastodon.client.entities.api.Reaction;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
import app.fedilab.android.mastodon.helper.ThemeHelper; import app.fedilab.android.mastodon.helper.ThemeHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AnnouncementsVM; import app.fedilab.android.mastodon.viewmodel.mastodon.AnnouncementsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM;
import app.fedilab.android.mastodon.viewmodel.pleroma.ActionsVM; import app.fedilab.android.mastodon.viewmodel.pleroma.ActionsVM;
@ -46,18 +47,29 @@ public class ReactionAdapter extends RecyclerView.Adapter<ReactionAdapter.Reacti
private final List<Reaction> reactions; private final List<Reaction> reactions;
private final String announcementId; private final String announcementId;
private final boolean statusReaction; private final boolean statusReaction;
private final boolean isPleroma;
private Context context; private Context context;
ReactionAdapter(String announcementId, List<Reaction> reactions, boolean statusReaction, boolean isPleroma) {
this.reactions = reactions;
this.announcementId = announcementId;
this.statusReaction = statusReaction;
this.isPleroma = isPleroma;
}
ReactionAdapter(String announcementId, List<Reaction> reactions, boolean statusReaction) { ReactionAdapter(String announcementId, List<Reaction> reactions, boolean statusReaction) {
this.reactions = reactions; this.reactions = reactions;
this.announcementId = announcementId; this.announcementId = announcementId;
this.statusReaction = statusReaction; this.statusReaction = statusReaction;
this.isPleroma = true;
} }
ReactionAdapter(String announcementId, List<Reaction> reactions) { ReactionAdapter(String announcementId, List<Reaction> reactions) {
this.reactions = reactions; this.reactions = reactions;
this.announcementId = announcementId; this.announcementId = announcementId;
this.statusReaction = false; this.statusReaction = false;
this.isPleroma = true;
} }
@NonNull @NonNull
@ -101,7 +113,7 @@ public class ReactionAdapter extends RecyclerView.Adapter<ReactionAdapter.Reacti
} }
notifyItemChanged(position); notifyItemChanged(position);
}); });
} else { } else if (isPleroma) {
ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
holder.binding.reactionContainer.setOnClickListener(v -> { holder.binding.reactionContainer.setOnClickListener(v -> {
if (reaction.me) { if (reaction.me) {
@ -115,6 +127,20 @@ public class ReactionAdapter extends RecyclerView.Adapter<ReactionAdapter.Reacti
} }
notifyItemChanged(position); notifyItemChanged(position);
}); });
} else {
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
holder.binding.reactionContainer.setOnClickListener(v -> {
if (reaction.me) {
statusesVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, announcementId, reaction.name);
reaction.me = false;
reaction.count -= 1;
} else {
statusesVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, announcementId, reaction.name);
reaction.me = true;
reaction.count += 1;
}
notifyItemChanged(position);
});
} }
} }

View file

@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collections; import java.util.Collections;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.DrawerReorderBinding; import app.fedilab.android.databinding.DrawerReorderBinding;
import app.fedilab.android.mastodon.activities.ReorderTimelinesActivity; import app.fedilab.android.mastodon.activities.ReorderTimelinesActivity;
import app.fedilab.android.mastodon.client.entities.app.BottomMenu; import app.fedilab.android.mastodon.client.entities.app.BottomMenu;
@ -100,6 +101,8 @@ public class ReorderBottomMenuAdapter extends RecyclerView.Adapter<RecyclerView.
holder.binding.hide.setOnClickListener(v -> { holder.binding.hide.setOnClickListener(v -> {
bottomMenu.bottom_menu.get(position).visible = !bottomMenu.bottom_menu.get(position).visible; bottomMenu.bottom_menu.get(position).visible = !bottomMenu.bottom_menu.get(position).visible;
bottomMenu.user_id = MainActivity.currentUserID;
bottomMenu.instance = MainActivity.currentInstance;
if (bottomMenu.bottom_menu.get(position).visible) { if (bottomMenu.bottom_menu.get(position).visible) {
holder.binding.hide.setImageResource(R.drawable.ic_baseline_visibility_24); holder.binding.hide.setImageResource(R.drawable.ic_baseline_visibility_24);
} else { } else {

View file

@ -50,6 +50,7 @@ import android.os.Looper;
import android.text.Html; import android.text.Html;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -414,6 +415,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
boolean compactButtons = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_COMPACT_ACTION_BUTTON), false); boolean compactButtons = sharedpreferences.getBoolean(context.getString(R.string.SET_DISPLAY_COMPACT_ACTION_BUTTON), false);
boolean originalDateForBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_BOOST_ORIGINAL_DATE), true); boolean originalDateForBoost = sharedpreferences.getBoolean(context.getString(R.string.SET_BOOST_ORIGINAL_DATE), true);
boolean hideSingleMediaWithCard = sharedpreferences.getBoolean(context.getString(R.string.SET_HIDE_SINGLE_MEDIA_WITH_CARD), false); boolean hideSingleMediaWithCard = sharedpreferences.getBoolean(context.getString(R.string.SET_HIDE_SINGLE_MEDIA_WITH_CARD), false);
boolean autofetch = sharedpreferences.getBoolean(context.getString(R.string.SET_AUTO_FETCH_MISSING_MESSAGES), false);
if (compactButtons) { if (compactButtons) {
ConstraintSet set = new ConstraintSet(); ConstraintSet set = new ConstraintSet();
@ -517,7 +520,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.quotedMessage.cardviewContainer.setVisibility(View.GONE); holder.binding.quotedMessage.cardviewContainer.setVisibility(View.GONE);
} }
if (currentAccount != null && currentAccount.api == Account.API.PLEROMA) { if (currentAccount != null && currentAccount.api == Account.API.PLEROMA || status.reactions != null) {
if (status.pleroma != null && status.pleroma.emoji_reactions != null && status.pleroma.emoji_reactions.size() > 0) { if (status.pleroma != null && status.pleroma.emoji_reactions != null && status.pleroma.emoji_reactions.size() > 0) {
holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE); holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE);
ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.pleroma.emoji_reactions, true); ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.pleroma.emoji_reactions, true);
@ -525,6 +528,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
LinearLayoutManager layoutManager LinearLayoutManager layoutManager
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); = new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager); holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager);
} else if (status.reactions != null && status.reactions.size() > 0) {
holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE);
ReactionAdapter reactionAdapter = new ReactionAdapter(status.id, status.reactions, true, false);
holder.binding.layoutReactions.reactionsView.setAdapter(reactionAdapter);
LinearLayoutManager layoutManager
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
holder.binding.layoutReactions.reactionsView.setLayoutManager(layoutManager);
} else { } else {
holder.binding.layoutReactions.getRoot().setVisibility(View.GONE); holder.binding.layoutReactions.getRoot().setVisibility(View.GONE);
holder.binding.layoutReactions.reactionsView.setAdapter(null); holder.binding.layoutReactions.reactionsView.setAdapter(null);
@ -537,33 +547,57 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}).setOnEmojiClickListener((emoji, imageView) -> { }).setOnEmojiClickListener((emoji, imageView) -> {
String emojiStr = imageView.getUnicode(); String emojiStr = imageView.getUnicode();
boolean alreadyAdded = false; boolean alreadyAdded = false;
if (status.pleroma == null || status.pleroma.emoji_reactions == null) { if (status.pleroma != null && status.pleroma.emoji_reactions != null) {
return; for (Reaction reaction : status.pleroma.emoji_reactions) {
} if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
for (Reaction reaction : status.pleroma.emoji_reactions) { alreadyAdded = true;
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { reaction.count = (reaction.count - 1);
alreadyAdded = true; if (reaction.count == 0) {
reaction.count = (reaction.count - 1); status.pleroma.emoji_reactions.remove(reaction);
if (reaction.count == 0) { }
status.pleroma.emoji_reactions.remove(reaction); adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
} }
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
} }
} if (!alreadyAdded) {
if (!alreadyAdded) { Reaction reaction = new Reaction();
Reaction reaction = new Reaction(); reaction.me = true;
reaction.me = true; reaction.count = 1;
reaction.count = 1; reaction.name = emojiStr;
reaction.name = emojiStr; status.pleroma.emoji_reactions.add(0, reaction);
status.pleroma.emoji_reactions.add(0, reaction); adapter.notifyItemChanged(holder.getBindingAdapterPosition());
adapter.notifyItemChanged(holder.getBindingAdapterPosition()); }
} ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); if (alreadyAdded) {
if (alreadyAdded) { actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); } else {
} else { actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); }
} else if (status.reactions != null) {
for (Reaction reaction : status.reactions) {
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.reactions.remove(reaction);
}
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
}
}
if (!alreadyAdded) {
Reaction reaction = new Reaction();
reaction.me = true;
reaction.count = 1;
reaction.name = emojiStr;
status.reactions.add(0, reaction);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}
if (alreadyAdded) {
statusesVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
statusesVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
}
} }
}) })
.build(holder.binding.layoutReactions.fakeEdittext); .build(holder.binding.layoutReactions.fakeEdittext);
@ -589,32 +623,61 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
String url = emojis.get(BaseMainActivity.currentInstance).get(index).url; String url = emojis.get(BaseMainActivity.currentInstance).get(index).url;
String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url; String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url;
boolean alreadyAdded = false; boolean alreadyAdded = false;
for (Reaction reaction : status.pleroma.emoji_reactions) { if (status.pleroma != null && status.pleroma.emoji_reactions != null) {
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) { for (Reaction reaction : status.pleroma.emoji_reactions) {
alreadyAdded = true; if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
reaction.count = (reaction.count - 1); alreadyAdded = true;
if (reaction.count == 0) { reaction.count = (reaction.count - 1);
status.pleroma.emoji_reactions.remove(reaction); if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
}
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
} }
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
} }
} if (!alreadyAdded) {
if (!alreadyAdded) { Reaction reaction = new Reaction();
Reaction reaction = new Reaction(); reaction.me = true;
reaction.me = true; reaction.count = 1;
reaction.count = 1; reaction.name = emojiStr;
reaction.name = emojiStr; reaction.url = url;
reaction.url = url; reaction.static_url = static_url;
reaction.static_url = static_url; status.pleroma.emoji_reactions.add(0, reaction);
status.pleroma.emoji_reactions.add(0, reaction); adapter.notifyItemChanged(holder.getBindingAdapterPosition());
adapter.notifyItemChanged(holder.getBindingAdapterPosition()); }
} ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class); if (alreadyAdded) {
if (alreadyAdded) { actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); } else {
} else { actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr); }
} else if (status.reactions != null) {
for (Reaction reaction : status.reactions) {
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.reactions.remove(reaction);
}
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
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.reactions.add(0, reaction);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}
if (alreadyAdded) {
statusesVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
statusesVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
}
} }
}); });
gridView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp); gridView.setPadding(paddingDp, paddingDp, paddingDp, paddingDp);
@ -1067,7 +1130,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} }
//Button sizes depending of the defined scale //Button sizes depending of the defined scale
float normalSize = Helper.convertDpToPixel(28, context); float normalSize = Helper.convertDpToPixel(28, context);
holder.binding.actionButtonReply.getLayoutParams().width = (int) (normalSize * scaleIcon); holder.binding.actionButtonReply.getLayoutParams().width = (int) (normalSize * scaleIcon);
holder.binding.actionButtonReply.getLayoutParams().height = (int) (normalSize * scaleIcon); holder.binding.actionButtonReply.getLayoutParams().height = (int) (normalSize * scaleIcon);
holder.binding.actionButtonReply.requestLayout(); holder.binding.actionButtonReply.requestLayout();
@ -1080,6 +1142,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.actionButtonFavorite.setImageSize((int) (normalSize * scaleIcon)); holder.binding.actionButtonFavorite.setImageSize((int) (normalSize * scaleIcon));
holder.binding.actionButtonBookmark.setImageSize((int) (normalSize * scaleIcon)); holder.binding.actionButtonBookmark.setImageSize((int) (normalSize * scaleIcon));
holder.binding.statusAddCustomEmoji.getLayoutParams().width = (int) (normalSize * scaleIcon); holder.binding.statusAddCustomEmoji.getLayoutParams().width = (int) (normalSize * scaleIcon);
holder.binding.statusAddCustomEmoji.getLayoutParams().height = (int) (normalSize * scaleIcon); holder.binding.statusAddCustomEmoji.getLayoutParams().height = (int) (normalSize * scaleIcon);
holder.binding.statusAddCustomEmoji.requestLayout(); holder.binding.statusAddCustomEmoji.requestLayout();
@ -2095,44 +2158,65 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} }
if (status.isFetchMore && fetchMoreCallBack != null) { if (status.isFetchMore && fetchMoreCallBack != null) {
DrawerFetchMoreBinding drawerFetchMoreBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(context)); if (!autofetch) {
if (status.positionFetchMore == Status.PositionFetchMore.BOTTOM) { DrawerFetchMoreBinding drawerFetchMoreBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(context));
holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE); if (status.positionFetchMore == Status.PositionFetchMore.BOTTOM) {
holder.binding.fetchMoreContainerTop.setVisibility(View.VISIBLE); holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
holder.binding.fetchMoreContainerTop.removeAllViews(); holder.binding.fetchMoreContainerTop.setVisibility(View.VISIBLE);
holder.binding.fetchMoreContainerTop.addView(drawerFetchMoreBinding.getRoot()); holder.binding.fetchMoreContainerTop.removeAllViews();
} else { holder.binding.fetchMoreContainerTop.addView(drawerFetchMoreBinding.getRoot());
holder.binding.fetchMoreContainerBottom.setVisibility(View.VISIBLE); } else {
holder.binding.fetchMoreContainerTop.setVisibility(View.GONE); holder.binding.fetchMoreContainerBottom.setVisibility(View.VISIBLE);
holder.binding.fetchMoreContainerBottom.removeAllViews(); holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
holder.binding.fetchMoreContainerBottom.addView(drawerFetchMoreBinding.getRoot()); holder.binding.fetchMoreContainerBottom.removeAllViews();
} holder.binding.fetchMoreContainerBottom.addView(drawerFetchMoreBinding.getRoot());
drawerFetchMoreBinding.fetchMoreMin.setOnClickListener(v -> { }
status.isFetchMore = false; drawerFetchMoreBinding.fetchMoreMin.setOnClickListener(v -> {
int position = holder.getBindingAdapterPosition(); status.isFetchMore = false;
adapter.notifyItemChanged(position); int position = holder.getBindingAdapterPosition();
if (position < statusList.size() - 1) { adapter.notifyItemChanged(position);
if (position < statusList.size() - 1) {
String fromId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(position + 1).id;
} else {
fromId = status.id;
}
fetchMoreCallBack.onClickMinId(fromId, status);
}
});
drawerFetchMoreBinding.fetchMoreMax.setOnClickListener(v -> {
//We hide the button
status.isFetchMore = false;
String fromId; String fromId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) { if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(position + 1).id; fromId = statusList.get(holder.getBindingAdapterPosition()).id;
} else { } else {
fromId = status.id; fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
} }
fetchMoreCallBack.onClickMinId(fromId, status); fetchMoreCallBack.onClickMaxId(fromId, status);
} adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}); });
drawerFetchMoreBinding.fetchMoreMax.setOnClickListener(v -> { } else {
//We hide the button holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
status.isFetchMore = false; status.isFetchMore = false;
String fromId; int position = holder.getBindingAdapterPosition();
if (status.positionFetchMore == Status.PositionFetchMore.TOP) { String statusIdMin = null, statusIdMax;
fromId = statusList.get(holder.getBindingAdapterPosition()).id; if (position < statusList.size() - 1) {
} else { if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id; statusIdMin = statusList.get(position + 1).id;
} else {
statusIdMin = status.id;
}
} }
fetchMoreCallBack.onClickMaxId(fromId, status); if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
adapter.notifyItemChanged(holder.getBindingAdapterPosition()); statusIdMax = statusList.get(holder.getBindingAdapterPosition()).id;
}); } else {
statusIdMax = statusList.get(holder.getBindingAdapterPosition() - 1).id;
}
fetchMoreCallBack.autoFetch(statusIdMin, statusIdMax, status);
}
} else { } else {
holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE); holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
holder.binding.fetchMoreContainerTop.setVisibility(View.GONE); holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
@ -2236,6 +2320,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
if ((!fullAttachement || statusToDeal.sensitive) && !singleImage) { if ((!fullAttachement || statusToDeal.sensitive) && !singleImage) {
layoutMediaBinding.count.setText(String.format(Locale.getDefault(), "%d/%d", mediaPosition, statusToDeal.media_attachments.size())); layoutMediaBinding.count.setText(String.format(Locale.getDefault(), "%d/%d", mediaPosition, statusToDeal.media_attachments.size()));
} }
if (attachment.description != null && attachment.description.trim().length() > 0) {
layoutMediaBinding.media.setContentDescription(attachment.description.trim());
}
String finalUrl; String finalUrl;
if (attachment.url == null) { if (attachment.url == null) {
finalUrl = attachment.remote_url; finalUrl = attachment.remote_url;
@ -2590,32 +2677,53 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.bindingFilteredHide.dividerCard.setVisibility(View.GONE); holder.bindingFilteredHide.dividerCard.setVisibility(View.GONE);
} }
if (status.isFetchMore && fetchMoreCallBack != null) { if (status.isFetchMore && fetchMoreCallBack != null) {
holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE); boolean autofetch = sharedpreferences.getBoolean(context.getString(R.string.SET_AUTO_FETCH_MISSING_MESSAGES), false);
holder.bindingFilteredHide.layoutFetchMore.fetchMoreMin.setOnClickListener(v -> { if (!autofetch) {
status.isFetchMore = false; holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE);
notifyItemChanged(holder.getBindingAdapterPosition()); holder.bindingFilteredHide.layoutFetchMore.fetchMoreMin.setOnClickListener(v -> {
if (holder.getBindingAdapterPosition() < statusList.size() - 1) { status.isFetchMore = false;
notifyItemChanged(holder.getBindingAdapterPosition());
if (holder.getBindingAdapterPosition() < statusList.size() - 1) {
String fromId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(holder.getBindingAdapterPosition() + 1).id;
} else {
fromId = status.id;
}
fetchMoreCallBack.onClickMinId(fromId, status);
}
});
holder.bindingFilteredHide.layoutFetchMore.fetchMoreMax.setOnClickListener(v -> {
//We hide the button
status.isFetchMore = false;
notifyItemChanged(holder.getBindingAdapterPosition());
String fromId; String fromId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) { if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(holder.getBindingAdapterPosition() + 1).id; fromId = statusList.get(holder.getBindingAdapterPosition()).id;
} else { } else {
fromId = status.id; fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
} }
fetchMoreCallBack.onClickMinId(fromId, status); fetchMoreCallBack.onClickMaxId(fromId, status);
}
}); });
holder.bindingFilteredHide.layoutFetchMore.fetchMoreMax.setOnClickListener(v -> { } else {
//We hide the button
status.isFetchMore = false; status.isFetchMore = false;
String fromId; String minId = null, maxId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) { if (holder.getBindingAdapterPosition() < statusList.size() - 1) {
fromId = statusList.get(holder.getBindingAdapterPosition()).id; if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
} else { minId = statusList.get(holder.getBindingAdapterPosition() + 1).id;
fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id; } else {
minId = status.id;
}
} }
fetchMoreCallBack.onClickMaxId(fromId, status); if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
notifyItemChanged(holder.getBindingAdapterPosition()); maxId = statusList.get(holder.getBindingAdapterPosition()).id;
}); } else {
maxId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
}
fetchMoreCallBack.autoFetch(minId, maxId, status);
}
} else { } else {
holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE); holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.GONE);
} }
@ -2771,6 +2879,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
void onClickMinId(String min_id, Status statusToUpdate); void onClickMinId(String min_id, Status statusToUpdate);
void onClickMaxId(String max_id, Status statusToUpdate); void onClickMaxId(String max_id, Status statusToUpdate);
void autoFetch(String min_id, String max_id, Status status);
} }
public static class StatusViewHolder extends RecyclerView.ViewHolder { public static class StatusViewHolder extends RecyclerView.ViewHolder {

View file

@ -23,9 +23,10 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference; import androidx.preference.SwitchPreference;
import androidx.preference.SwitchPreferenceCompat; import androidx.work.Data;
import androidx.work.WorkManager; import androidx.work.WorkManager;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.mastodon.helper.Helper; import app.fedilab.android.mastodon.helper.Helper;
@ -53,7 +54,7 @@ public class FragmentHomeCacheSettings extends PreferenceFragmentCompat implemen
return; return;
} }
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
SwitchPreferenceCompat SET_FETCH_HOME = findPreference(getString(R.string.SET_FETCH_HOME)); SwitchPreference SET_FETCH_HOME = findPreference(getString(R.string.SET_FETCH_HOME));
if (SET_FETCH_HOME != null) { if (SET_FETCH_HOME != null) {
boolean checked = sharedpreferences.getBoolean(getString(R.string.SET_FETCH_HOME) + MainActivity.currentUserID + MainActivity.currentInstance, false); boolean checked = sharedpreferences.getBoolean(getString(R.string.SET_FETCH_HOME) + MainActivity.currentUserID + MainActivity.currentInstance, false);
SET_FETCH_HOME.setChecked(checked); SET_FETCH_HOME.setChecked(checked);
@ -69,27 +70,33 @@ public class FragmentHomeCacheSettings extends PreferenceFragmentCompat implemen
@Override @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (getActivity() != null) {
if (key.compareToIgnoreCase(getString(R.string.SET_FETCH_HOME)) == 0) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
SharedPreferences.Editor editor = sharedPreferences.edit(); Data inputData = new Data.Builder()
SwitchPreference SET_FETCH_HOME = findPreference(getString(R.string.SET_FETCH_HOME)); .putString(Helper.ARG_INSTANCE, BaseMainActivity.currentInstance)
if (SET_FETCH_HOME != null) { .putString(Helper.ARG_USER_ID, BaseMainActivity.currentUserID)
editor.putBoolean(getString(R.string.SET_FETCH_HOME) + MainActivity.currentUserID + MainActivity.currentInstance, SET_FETCH_HOME.isChecked()); .build();
editor.commit(); if (key.compareToIgnoreCase(getString(R.string.SET_FETCH_HOME)) == 0) {
if (SET_FETCH_HOME.isChecked()) { SharedPreferences.Editor editor = sharedpreferences.edit();
FetchHomeWorker.setRepeatHome(requireActivity(), MainActivity.currentAccount); SwitchPreference SET_FETCH_HOME = findPreference(getString(R.string.SET_FETCH_HOME));
} else { if (SET_FETCH_HOME != null) {
WorkManager.getInstance(requireActivity()).cancelAllWorkByTag(Helper.WORKER_REFRESH_HOME + MainActivity.currentUserID + MainActivity.currentInstance); editor.putBoolean(getString(R.string.SET_FETCH_HOME) + MainActivity.currentUserID + MainActivity.currentInstance, SET_FETCH_HOME.isChecked());
editor.commit();
if (SET_FETCH_HOME.isChecked()) {
FetchHomeWorker.setRepeatHome(requireActivity(), MainActivity.currentAccount, inputData);
} else {
WorkManager.getInstance(requireActivity()).cancelAllWorkByTag(Helper.WORKER_REFRESH_HOME + MainActivity.currentUserID + MainActivity.currentInstance);
}
} }
} }
} if (key.compareToIgnoreCase(getString(R.string.SET_FETCH_HOME_DELAY_VALUE)) == 0) {
if (key.compareToIgnoreCase(getString(R.string.SET_FETCH_HOME_DELAY_VALUE)) == 0) { ListPreference SET_FETCH_HOME_DELAY_VALUE = findPreference(getString(R.string.SET_FETCH_HOME_DELAY_VALUE));
ListPreference SET_FETCH_HOME_DELAY_VALUE = findPreference(getString(R.string.SET_FETCH_HOME_DELAY_VALUE)); if (SET_FETCH_HOME_DELAY_VALUE != null) {
if (SET_FETCH_HOME_DELAY_VALUE != null) { SharedPreferences.Editor editor = sharedpreferences.edit();
SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putString(getString(R.string.SET_FETCH_HOME_DELAY_VALUE) + MainActivity.currentUserID + MainActivity.currentInstance, SET_FETCH_HOME_DELAY_VALUE.getValue());
editor.putString(getString(R.string.SET_FETCH_HOME_DELAY_VALUE) + MainActivity.currentUserID + MainActivity.currentInstance, SET_FETCH_HOME_DELAY_VALUE.getValue()); editor.commit();
editor.commit(); FetchHomeWorker.setRepeatHome(requireActivity(), MainActivity.currentAccount, inputData);
FetchHomeWorker.setRepeatHome(requireActivity(), MainActivity.currentAccount); }
} }
} }
} }

View file

@ -154,7 +154,7 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat {
Preference pref_export_settings = findPreference(getString(R.string.pref_export_settings)); Preference pref_export_settings = findPreference(getString(R.string.pref_export_settings));
if (pref_export_settings != null) { if (pref_export_settings != null) {
pref_export_settings.setOnPreferenceClickListener(preference -> { pref_export_settings.setOnPreferenceClickListener(preference -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else { } else {
try { try {

View file

@ -75,7 +75,7 @@ import es.dmoral.toasty.Toasty;
public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.FetchMoreCallBack { public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.FetchMoreCallBack {
private boolean scrollingUp;
private static final int PRELOAD_AHEAD_ITEMS = 10; private static final int PRELOAD_AHEAD_ITEMS = 10;
public UpdateCounters update; public UpdateCounters update;
private FragmentPaginationBinding binding; private FragmentPaginationBinding binding;
@ -506,9 +506,9 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
} else if (update != null && insertedStatus == 0 && direction == DIRECTION.REFRESH) { } else if (update != null && insertedStatus == 0 && direction == DIRECTION.REFRESH) {
update.onUpdate(0, timelineType, slug); update.onUpdate(0, timelineType, slug);
} }
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
if (direction == DIRECTION.TOP && fetchingMissing) { if (direction == DIRECTION.TOP && fetchingMissing) {
int position = getAbsolutePosition(fetched_statuses.statuses.get(fetched_statuses.statuses.size() - 1)); int position = getAbsolutePosition(fetched_statuses.statuses.get(fetched_statuses.statuses.size() - 1));
if (position != -1) { if (position != -1) {
binding.recyclerView.scrollToPosition(position + 1); binding.recyclerView.scrollToPosition(position + 1);
} }
@ -656,7 +656,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override @Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
scrollingUp = dy < 0;
if (requireActivity() instanceof BaseMainActivity) { if (requireActivity() instanceof BaseMainActivity) {
if (dy < 0 && !((BaseMainActivity) requireActivity()).getFloatingVisibility()) if (dy < 0 && !((BaseMainActivity) requireActivity()).getFloatingVisibility())
((BaseMainActivity) requireActivity()).manageFloatingButton(true); ((BaseMainActivity) requireActivity()).manageFloatingButton(true);
@ -1228,6 +1228,17 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
route(DIRECTION.BOTTOM, true, statusToUpdate); route(DIRECTION.BOTTOM, true, statusToUpdate);
} }
@Override
public void autoFetch(String min_id, String max_id, Status statusToUpdate) {
if (scrollingUp) {
min_id_fetch_more = min_id;
route(DIRECTION.TOP, true, statusToUpdate);
} else {
max_id_fetch_more = max_id;
route(DIRECTION.BOTTOM, true, statusToUpdate);
}
}
public enum DIRECTION { public enum DIRECTION {
TOP, TOP,
BOTTOM, BOTTOM,

View file

@ -30,6 +30,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.mastodon.client.endpoints.MastodonAnnouncementsService;
import app.fedilab.android.mastodon.client.endpoints.MastodonStatusesService; import app.fedilab.android.mastodon.client.endpoints.MastodonStatusesService;
import app.fedilab.android.mastodon.client.entities.api.Account; import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Accounts; import app.fedilab.android.mastodon.client.entities.api.Accounts;
@ -1292,4 +1293,48 @@ public class StatusesVM extends AndroidViewModel {
}).start(); }).start();
return voidMutableLiveData; return voidMutableLiveData;
} }
/**
* React to a status 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) {
MastodonStatusesService mastodonStatusesService = init(instance);
new Thread(() -> {
Call<Void> addReactionCall = mastodonStatusesService.addReaction(token, id, name);
if (addReactionCall != null) {
try {
addReactionCall.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
/**
* Undo a react emoji to a status.
*
* @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) {
MastodonStatusesService mastodonStatusesService = init(instance);
new Thread(() -> {
Call<Void> removeReactionCall = mastodonStatusesService.removeReaction(token, id, name);
if (removeReactionCall != null) {
try {
removeReactionCall.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
} }

View file

@ -821,7 +821,7 @@ public class Helper {
public static void requestPermissionAndProceed(Activity activity, PermissionGranted permissionGranted) { public static void requestPermissionAndProceed(Activity activity, PermissionGranted permissionGranted) {
if (Build.VERSION.SDK_INT >= 23) { if (Build.VERSION.SDK_INT >= 23) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, app.fedilab.android.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE); ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, app.fedilab.android.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE);
} else { } else {

View file

@ -93,6 +93,15 @@ public class Sqlite extends SQLiteOpenHelper {
public static final String COL_ABOUT = "ABOUT"; public static final String COL_ABOUT = "ABOUT";
public static final String COL_USER_INSTANCE = "USER_INSTANCE"; public static final String COL_USER_INSTANCE = "USER_INSTANCE";
//Home fetch logs
public static final String TABLE_HOME_FETCH_LOGS = "TABLE_HOME_FETCH_LOGS";
public static final String COL_INSERTED = "INSERTED";
public static final String COL_UPDATED = "UPDATED";
public static final String COL_FAILED = "FAILED";
public static final String COL_FREQUENCY = "FREQUENCY";
public static final String COL_FETCHED_COUNT = "FETCHED_COUNT";
private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " (" private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " ("
+ COL_USER_ID + " TEXT NOT NULL, " + COL_USER_ID + " TEXT NOT NULL, "
+ COL_INSTANCE + " TEXT NOT NULL, " + COL_INSTANCE + " TEXT NOT NULL, "
@ -192,8 +201,8 @@ public class Sqlite extends SQLiteOpenHelper {
+ COL_USER_ID + " TEXT NOT NULL, " + COL_USER_ID + " TEXT NOT NULL, "
+ COL_TYPE + " TEXT NOT NULL, " + COL_TYPE + " TEXT NOT NULL, "
+ COL_MUTED_ACCOUNTS + " TEXT)"; + COL_MUTED_ACCOUNTS + " TEXT)";
public static SQLiteDatabase db;
private static Sqlite sInstance;
private final String CREATE_TABLE_STORED_INSTANCES = "CREATE TABLE " private final String CREATE_TABLE_STORED_INSTANCES = "CREATE TABLE "
+ TABLE_BOOKMARKED_INSTANCES + "(" + TABLE_BOOKMARKED_INSTANCES + "("
+ COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@ -202,6 +211,11 @@ public class Sqlite extends SQLiteOpenHelper {
+ COL_ABOUT + " TEXT NOT NULL, " + COL_ABOUT + " TEXT NOT NULL, "
+ COL_USER_INSTANCE + " TEXT NOT NULL)"; + COL_USER_INSTANCE + " TEXT NOT NULL)";
public static SQLiteDatabase db;
private static Sqlite sInstance;
public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version); super(context, name, factory, version);
} }

View file

@ -237,7 +237,7 @@ public class FragmentLoginMain extends Fragment {
} }
}); });
} else if (itemId == R.id.action_import_data) { } else if (itemId == R.id.action_import_data) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else { } else {
proceed(); proceed();

View file

@ -2,10 +2,10 @@
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:autoMirrored="true" android:autoMirrored="true"
android:tint="?attr/colorControlNormal" android:tint="?colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" /> android:pathData="M14.91,6.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L8.91,11.3c-0.39,0.39 -0.39,1.02 0,1.41l4.59,4.59c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L11.03,12l3.88,-3.88c0.38,-0.39 0.38,-1.03 0,-1.41z" />
</vector> </vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M9.31,6.71c-0.39,0.39 -0.39,1.02 0,1.41L13.19,12l-3.88,3.88c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0l4.59,-4.59c0.39,-0.39 0.39,-1.02 0,-1.41L10.72,6.7c-0.38,-0.38 -1.02,-0.38 -1.41,0.01z" />
</vector>

View file

@ -21,7 +21,7 @@
android:text="@string/bookmarks" android:text="@string/bookmarks"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" app:iconGravity="end"
app:iconTint="?attr/colorAccent" app:iconTint="?attr/colorAccent"
app:strokeColor="?attr/colorAccent" /> app:strokeColor="?attr/colorAccent" />
@ -36,7 +36,7 @@
android:text="@string/muted_menu" android:text="@string/muted_menu"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" app:iconGravity="end"
app:iconTint="?attr/colorAccent" app:iconTint="?attr/colorAccent"
app:strokeColor="?attr/colorAccent" /> app:strokeColor="?attr/colorAccent" />
@ -51,7 +51,7 @@
android:text="@string/muted_menu_home" android:text="@string/muted_menu_home"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" app:iconGravity="end"
app:iconTint="?attr/colorAccent" app:iconTint="?attr/colorAccent"
app:strokeColor="?attr/colorAccent" /> app:strokeColor="?attr/colorAccent" />
@ -66,7 +66,7 @@
android:text="@string/blocked_menu" android:text="@string/blocked_menu"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" app:iconGravity="end"
app:iconTint="?attr/colorAccent" app:iconTint="?attr/colorAccent"
app:strokeColor="?attr/colorAccent" /> app:strokeColor="?attr/colorAccent" />
@ -82,7 +82,7 @@
android:text="@string/favourite" android:text="@string/favourite"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" app:iconGravity="end"
app:iconTint="?attr/colorAccent" app:iconTint="?attr/colorAccent"
app:strokeColor="?attr/colorAccent" /> app:strokeColor="?attr/colorAccent" />
@ -98,7 +98,7 @@
android:text="@string/blocked_domains" android:text="@string/blocked_domains"
android:textAlignment="textStart" android:textAlignment="textStart"
android:textColor="?attr/colorAccent" android:textColor="?attr/colorAccent"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" app:iconGravity="end"
app:iconTint="?attr/colorAccent" app:iconTint="?attr/colorAccent"
app:strokeColor="?attr/colorAccent" /> app:strokeColor="?attr/colorAccent" />

View file

@ -20,7 +20,7 @@
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:text="@string/reports" android:text="@string/reports"
android:textAlignment="textStart" android:textAlignment="textStart"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" /> app:iconGravity="end" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -32,7 +32,7 @@
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:text="@string/accounts" android:text="@string/accounts"
android:textAlignment="textStart" android:textAlignment="textStart"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" /> app:iconGravity="end" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -44,7 +44,7 @@
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:text="@string/blocked_domains" android:text="@string/blocked_domains"
android:textAlignment="textStart" android:textAlignment="textStart"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" /> app:iconGravity="end" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -16,15 +16,15 @@
--> -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="wrap_content">
<DatePicker <DatePicker
android:id="@+id/date_picker" android:id="@+id/date_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:calendarViewShown="true" android:datePickerMode="calendar"
android:spinnersShown="false"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -47,67 +47,58 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/date_time_cancel" android:id="@+id/date_time_cancel"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_marginVertical="12dp"
android:layout_marginStart="12dp"
android:text="@string/cancel" android:text="@string/cancel"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date_time_previous"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom" /> app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/date_time_previous" android:id="@+id/date_time_previous"
style="@style/Widget.Material3.Button.Icon" style="@style/Widget.Material3.Button.IconButton.Outlined"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_margin="12dp"
android:contentDescription="@string/previous" android:contentDescription="@string/previous"
android:padding="0dp"
android:visibility="gone" android:visibility="gone"
app:icon="@drawable/ic_baseline_skip_previous_24" app:icon="@drawable/ic_navigate_before"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date_time_next" app:layout_constraintEnd_toStartOf="@id/date_time_next"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/date_time_cancel" app:layout_constraintStart_toEndOf="@id/date_time_cancel"
app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom" /> app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/date_time_next" android:id="@+id/date_time_next"
style="@style/Widget.Material3.Button.Icon" style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_margin="12dp"
android:contentDescription="@string/next" android:contentDescription="@string/next"
android:padding="0dp" app:icon="@drawable/ic_navigate_next"
app:icon="@drawable/ic_baseline_skip_next_24"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date_time_set" app:layout_constraintEnd_toStartOf="@id/date_time_set"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/date_time_previous"
app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom" /> app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/date_time_set" android:id="@+id/date_time_set"
style="@style/Widget.Material3.Button.Icon" style="@style/Widget.Material3.Button.IconButton.Filled"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="40dp" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_marginVertical="12dp"
android:contentDescription="@string/validate" android:layout_marginEnd="12dp"
android:padding="0dp" android:contentDescription="@string/schedule"
android:visibility="gone" android:visibility="gone"
app:icon="@drawable/ic_baseline_check_24" app:icon="@drawable/ic_baseline_check_24"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom"
app:layout_constraintStart_toEndOf="@id/date_time_next" tools:visibility="visible" />
app:layout_constraintTop_toBottomOf="@id/barrier_date_time_bottom" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -8,5 +8,5 @@
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:textAlignment="textStart" android:textAlignment="textStart"
app:icon="@drawable/ic_baseline_navigate_next_24" app:icon="@drawable/ic_navigate_next"
app:iconGravity="end" /> app:iconGravity="end" />

View file

@ -630,10 +630,10 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"> app:layout_constraintVertical_bias="0.0">
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/action_button_reply" android:id="@+id/action_button_reply"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"
@ -671,10 +671,10 @@
app:primaryColor="@color/boost_icon" app:primaryColor="@color/boost_icon"
app:secondaryColor="@color/boost_icon" /> app:secondaryColor="@color/boost_icon" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/action_button_quote" android:id="@+id/action_button_quote"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"
@ -726,10 +726,10 @@
sparkbutton:iconSize="28dp" /> sparkbutton:iconSize="28dp" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/action_button_translate" android:id="@+id/action_button_translate"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"
@ -745,10 +745,10 @@
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/action_button_maths" android:id="@+id/action_button_maths"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"
@ -763,10 +763,10 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_add_custom_emoji" android:id="@+id/status_add_custom_emoji"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"
@ -781,10 +781,10 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/status_emoji" android:id="@+id/status_emoji"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"
@ -799,10 +799,10 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/action_button_more" android:id="@+id/action_button_more"
android:layout_width="48dp" android:layout_width="28dp"
android:layout_height="48dp" android:layout_height="28dp"
android:layout_gravity="center|end" android:layout_gravity="center|end"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@color/transparent" android:background="@color/transparent"

View file

@ -41,14 +41,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<de.timfreiheit.mathjax.android.MathJaxView
android:id="@+id/laTexView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:automaticLinebreaks="true"
app:input="TeX"
app:output="SVG" />
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="fetch_home_every">Fetch Home every</string>
<string name="type_of_home_delay_title">Home fetch time</string>
<string name="set_fetch_home">Automatically fetch home messages</string>
<string name="home_cache">Home cache</string>
</resources> </resources>

View file

@ -999,4 +999,11 @@
<string name="following">Sledující</string> <string name="following">Sledující</string>
<string name="toast_error_peertube_not_supported">Vaše instance Peertube je příliš stará a nemůže být aplikací podporována.</string> <string name="toast_error_peertube_not_supported">Vaše instance Peertube je příliš stará a nemůže být aplikací podporována.</string>
<string name="compose_shortcut_short_label1">Vytváření</string> <string name="compose_shortcut_short_label1">Vytváření</string>
<string name="otp_message">Token pro dvoufaktorovou autentizaci</string>
<string name="fetch_home_every">Načíst Domov každých</string>
<string name="home_cache">Cache pro Domov</string>
<string name="type_of_home_delay_title">Čas načtení Domova</string>
<string name="set_fetch_home">Automaticky načítat domácí zprávy</string>
<string name="fetch_home_messages">Načíst domácí zprávy</string>
<string name="auto_fetch_missing">Automaticky načítat chybějící zprávy</string>
</resources> </resources>

View file

@ -989,4 +989,11 @@
<string name="about_peertube">\"PeerTube ist ein Tool zum Teilen von Online-Videos, das von Framasoft entwickelt wurde, einer französischen Non-Profit-Organisation....PeerTube ermöglicht es, Plattformen miteinander zu verbinden und so ein großes Netzwerk von Plattformen zu schaffen, die sowohl autonom als auch miteinander verbunden sind.\"</string> <string name="about_peertube">\"PeerTube ist ein Tool zum Teilen von Online-Videos, das von Framasoft entwickelt wurde, einer französischen Non-Profit-Organisation....PeerTube ermöglicht es, Plattformen miteinander zu verbinden und so ein großes Netzwerk von Plattformen zu schaffen, die sowohl autonom als auch miteinander verbunden sind.\"</string>
<string name="compose_shortcut_short_label1">Entwerfen</string> <string name="compose_shortcut_short_label1">Entwerfen</string>
<string name="toast_error_peertube_not_supported">Ihr Peertube ist zu alt und wird von der App nicht unterstützt.</string> <string name="toast_error_peertube_not_supported">Ihr Peertube ist zu alt und wird von der App nicht unterstützt.</string>
<string name="set_fetch_home">Automatisch Beiträge der Startseite abrufen</string>
<string name="type_of_home_delay_title">Aktualisierungsintervall der Startseite</string>
<string name="auto_fetch_missing">Automatisch fehlende Beiträge der Startseite abrufen</string>
<string name="fetch_home_every">Startseite aktualisieren alle</string>
<string name="otp_message">Zwei-Faktor-Authentifizierungstoken</string>
<string name="home_cache">Cache der Startseite</string>
<string name="fetch_home_messages">Beiträge auf der Startseite abrufen</string>
</resources> </resources>

View file

@ -983,4 +983,11 @@
<string name="about_peertube">\"PeerTube é unha ferramenta desenvolta por Framasoft (organización sen ánimo de lucro) para compartir vídeos en internet. PeerTube permite que unhas plataformas se conecten con outras, creando unha gran rede na que as partes son autónomas e están interconectadas.\"</string> <string name="about_peertube">\"PeerTube é unha ferramenta desenvolta por Framasoft (organización sen ánimo de lucro) para compartir vídeos en internet. PeerTube permite que unhas plataformas se conecten con outras, creando unha gran rede na que as partes son autónomas e están interconectadas.\"</string>
<string name="compose_shortcut_short_label1">Redactar</string> <string name="compose_shortcut_short_label1">Redactar</string>
<string name="toast_error_peertube_not_supported">O teu Peertube é demasiado antigo e non pode usarse coa app.</string> <string name="toast_error_peertube_not_supported">O teu Peertube é demasiado antigo e non pode usarse coa app.</string>
<string name="otp_message">Token do segundo factor de autenticación</string>
<string name="fetch_home_every">Actualizar Inicio cada</string>
<string name="set_fetch_home">Actualizar automáticamente mensaxes de inicio</string>
<string name="type_of_home_delay_title">Intervalo de actualización</string>
<string name="home_cache">Caché do Inicio</string>
<string name="fetch_home_messages">Obter mensaxes</string>
<string name="auto_fetch_missing">Obter automáticamente as mensaxes que faltan</string>
</resources> </resources>

View file

@ -906,4 +906,6 @@
<string name="set_maths_support">Escrever fórmula</string> <string name="set_maths_support">Escrever fórmula</string>
<string name="show_privates">Mostrar mensagens diretas</string> <string name="show_privates">Mostrar mensagens diretas</string>
<string name="about_peertube">\"PeerTube é uma ferramenta para compartilhar vídeos online desenvolvida pela Framasoft, uma organização francesa sem fins lucrativos.…PeerTube permite que as plataformas sejam conectadas umas às outras, criando uma grande rede de plataformas autônomas e interconectadas.\"</string> <string name="about_peertube">\"PeerTube é uma ferramenta para compartilhar vídeos online desenvolvida pela Framasoft, uma organização francesa sem fins lucrativos.…PeerTube permite que as plataformas sejam conectadas umas às outras, criando uma grande rede de plataformas autônomas e interconectadas.\"</string>
<string name="toast_error_peertube_not_supported">Seu Peertube é muito antigo e não é suportado pelo aplicativo.</string>
<string name="otp_message">Token de autenticação de dois fatores</string>
</resources> </resources>

View file

@ -977,4 +977,14 @@
<string name="filter_languages">Filtra is limbas</string> <string name="filter_languages">Filtra is limbas</string>
<string name="translate_in">Borta in</string> <string name="translate_in">Borta in</string>
<string name="proxy_protocol_socks">SOCKS</string> <string name="proxy_protocol_socks">SOCKS</string>
<string name="about_peertube">\"PeerTube est unu traste pro cumpartzire vìdeos in lìnia isvilupadu dae Framasoft, un\'organizatzione frantzesa chene punna de lucru.…PeerTube permitit a sas prataformas de si collegare s\'una cun s\'àtera, creende una rete manna de prataformas chi sunt siat autònomas siat connessas.\"</string>
<string name="compose_shortcut_short_label1">Cumpone</string>
<string name="toast_error_peertube_not_supported">Su PeerTube tuo est tropu betzu e non podet èssere suportadu dae s\'aplicatzione.</string>
<string name="otp_message">Getone de autenticatzione a duos fatores</string>
<string name="fetch_home_messages">Recùpera is messàgios de sa lìnia printzipale</string>
<string name="auto_fetch_missing">Recùpera in automàticu is messàgios chi mancant</string>
<string name="fetch_home_every">Recùpera sa lìnia printzipale cada</string>
<string name="home_cache">Memòria temporànea lìnia printzipale</string>
<string name="type_of_home_delay_title">Tempus de recùperu de sa lìnia printzipale</string>
<string name="set_fetch_home">Recùpera in automàticu is messàgios de sa lìnia printzipale</string>
</resources> </resources>

View file

@ -987,4 +987,11 @@
<string name="about_peertube">\"PeerTube kar amacı gütmeyen bir Fransız kuruluşu olan Framasoft tarafından geliştirilen bir çevrim içi video paylaşım aracıdır.…PeerTube platformların birbirine bağlanmasına olanak tanıyarak hem özerk hem de birbirine bağlı büyük bir platform ağı oluşturur.\"</string> <string name="about_peertube">\"PeerTube kar amacı gütmeyen bir Fransız kuruluşu olan Framasoft tarafından geliştirilen bir çevrim içi video paylaşım aracıdır.…PeerTube platformların birbirine bağlanmasına olanak tanıyarak hem özerk hem de birbirine bağlı büyük bir platform ağı oluşturur.\"</string>
<string name="toast_error_peertube_not_supported">Peertube sürümünüz çok eski ve uygulama tarafından desteklenemiyor.</string> <string name="toast_error_peertube_not_supported">Peertube sürümünüz çok eski ve uygulama tarafından desteklenemiyor.</string>
<string name="compose_shortcut_short_label1">Oluştur</string> <string name="compose_shortcut_short_label1">Oluştur</string>
<string name="otp_message">İki aşamalı kimlik doğrulama belirteci</string>
<string name="fetch_home_every">Ana sayfayı alma sıklığı</string>
<string name="type_of_home_delay_title">Ana sayfa alma zamanı</string>
<string name="set_fetch_home">Ana sayfa mesajlarını otomatik olarak al</string>
<string name="home_cache">Ana sayfa önbelleği</string>
<string name="auto_fetch_missing">Eksik mesajları otomatik olarak al</string>
<string name="fetch_home_messages">Ana sayfa mesajlarını al</string>
</resources> </resources>

View file

@ -581,4 +581,21 @@
<string name="no_distributors_found">No distributors found!</string> <string name="no_distributors_found">No distributors found!</string>
<string name="no_distributors_explanation">You need a distributor for receiving push notifications.\nYou will find more details at %1$s.\n\nYou can also disable push notifications in settings for ignoring that message.</string> <string name="no_distributors_explanation">You need a distributor for receiving push notifications.\nYou will find more details at %1$s.\n\nYou can also disable push notifications in settings for ignoring that message.</string>
<string name="select_distributors">Select a distributor</string> <string name="select_distributors">Select a distributor</string>
<string name="account_approved">Обліковий запис затверджено</string>
<string name="admin_domainblock_domain">Блокування домену не перешкоджатиме створенню облікових записів у базі даних, але заднім числом автоматично застосовуватиме певні методи модерації до цих облікових записів.</string>
<string name="aggregate_notifications_summary">При ввімкненні застосунок згортає пов\'язані з ним сповіщення</string>
<string name="restart_the_app_theme">Для того, щоб зміни набули чинності, потрібно перезапустити застосунок.</string>
<string name="toast_token">Застосунок не зміг отримати токен</string>
<string name="restart_the_app">Перезапустити застосунок\?</string>
<string name="toast_error_fetch_message">Застосунок не знайшов віддалене повідомлення.</string>
<string name="set_extand_extra_features">Увімкнувши цю опцію, застосунок відображатиме додаткові функції. Ця функція передбачена для соціальних програм, таких як Pleroma, Akkoma або Glitch Social</string>
<string name="change_logo_description">Змінити логотип застосунку на вашому пристрої</string>
<string name="toast_error_peertube_not_supported">Ваш Peertube застарілий і не підтримується застосунком.</string>
<string name="my_app">Мій застосунок</string>
<string name="set_single_topbar">Якщо увімкнено, у застосунку буде лише одна смуга для часових шкал</string>
<string name="set_use_cache_indication">Часові шкали будуть кешуватися, щоб застосунок працював швидше.</string>
<string name="toast_error_add_to_list">Застосунку не вдалося додати обліковий запис до списку!</string>
<string name="toast_fetch_error">Застосунок не може знайти віддалені дані!</string>
<string name="set_remote_profile">Застосунок відображатиме профілі у відкритому доступі, щоб отримувати всі повідомлення. Взаємодія потребуватиме додаткового кроку для об\'єднання повідомлень.</string>
<string name="set_disable_release_notes_indication">Коли публікується нова версія, ви не отримаєте сповіщення в застосунку.</string>
</resources> </resources>

View file

@ -854,7 +854,7 @@
<string name="toast_unpin">消息不再置顶!</string> <string name="toast_unpin">消息不再置顶!</string>
<string name="toast_pin">消息已置顶</string> <string name="toast_pin">消息已置顶</string>
<string name="staff">职员</string> <string name="staff">职员</string>
<string name="approved">得到正式认可的</string> <string name="approved">已批准</string>
<string name="approve">批准</string> <string name="approve">批准</string>
<string name="set_unlisted_replies">不公开回复</string> <string name="set_unlisted_replies">不公开回复</string>
<string name="max_indentation_thread">同主题帖子中最大缩进</string> <string name="max_indentation_thread">同主题帖子中最大缩进</string>
@ -987,4 +987,11 @@
<string name="toast_error_peertube_not_supported">您的 Peertube 太旧,应用程序不支持。</string> <string name="toast_error_peertube_not_supported">您的 Peertube 太旧,应用程序不支持。</string>
<string name="customize_timelines">自定义时间线</string> <string name="customize_timelines">自定义时间线</string>
<string name="also_followed_by">关注者:</string> <string name="also_followed_by">关注者:</string>
<string name="otp_message">双因素身份验证令牌</string>
<string name="set_fetch_home">自动获取主页消息</string>
<string name="home_cache">主页缓存</string>
<string name="fetch_home_messages">获取主页消息</string>
<string name="type_of_home_delay_title">主页获取延迟</string>
<string name="fetch_home_every">获取主页消息每隔</string>
<string name="auto_fetch_missing">自动获取缺失的消息</string>
</resources> </resources>

View file

@ -1390,6 +1390,8 @@
<string name="SET_DISABLE_ANIMATED_EMOJI" translatable="false">SET_DISABLE_ANIMATED_EMOJI</string> <string name="SET_DISABLE_ANIMATED_EMOJI" translatable="false">SET_DISABLE_ANIMATED_EMOJI</string>
<string name="SET_CAPITALIZE" translatable="false">SET_CAPITALIZE</string> <string name="SET_CAPITALIZE" translatable="false">SET_CAPITALIZE</string>
<string name="SET_MENTIONS_AT_TOP" translatable="false">SET_MENTIONS_AT_TOP</string>
<string name="SET_THEME_BASE" translatable="false">SET_THEME_BASE</string> <string name="SET_THEME_BASE" translatable="false">SET_THEME_BASE</string>
<string name="SET_DYNAMICCOLOR" translatable="false">SET_DYNAMICCOLOR</string> <string name="SET_DYNAMICCOLOR" translatable="false">SET_DYNAMICCOLOR</string>
<string name="SET_CARDVIEW" translatable="false">SET_CARDVIEW</string> <string name="SET_CARDVIEW" translatable="false">SET_CARDVIEW</string>
@ -1476,6 +1478,7 @@
<string name="SET_INNER_MARKER" translatable="false">SET_INNER_MARKER</string> <string name="SET_INNER_MARKER" translatable="false">SET_INNER_MARKER</string>
<string name="SET_NOTIF_SILENT" translatable="false">SET_NOTIF_SILENT</string> <string name="SET_NOTIF_SILENT" translatable="false">SET_NOTIF_SILENT</string>
<string name="SET_REMEMBER_POSITION" translatable="false">SET_REMEMBER_POSITION</string> <string name="SET_REMEMBER_POSITION" translatable="false">SET_REMEMBER_POSITION</string>
<string name="SET_AUTO_FETCH_MISSING_MESSAGES" translatable="false">SET_AUTO_FETCH_MISSING_MESSAGES</string>
<string name="SET_EXPAND_CW" translatable="false">SET_EXPAND_CW</string> <string name="SET_EXPAND_CW" translatable="false">SET_EXPAND_CW</string>
<string name="SET_DISPLAY_ALL_NOTIFICATIONS_TYPE" translatable="false">SET_DISPLAY_ALL_NOTIFICATIONS_TYPE</string> <string name="SET_DISPLAY_ALL_NOTIFICATIONS_TYPE" translatable="false">SET_DISPLAY_ALL_NOTIFICATIONS_TYPE</string>
<string name="SET_EXCLUDED_NOTIFICATIONS_TYPE" translatable="false">SET_EXCLUDED_NOTIFICATIONS_TYPE</string> <string name="SET_EXCLUDED_NOTIFICATIONS_TYPE" translatable="false">SET_EXCLUDED_NOTIFICATIONS_TYPE</string>
@ -2236,4 +2239,14 @@
<string name="compose_shortcut_short_label1">Compose</string> <string name="compose_shortcut_short_label1">Compose</string>
<string name="toast_error_peertube_not_supported">Your Peertube is too old and cannot be supported by the app.</string> <string name="toast_error_peertube_not_supported">Your Peertube is too old and cannot be supported by the app.</string>
<string name="otp_message">Two factor authentication token</string> <string name="otp_message">Two factor authentication token</string>
<string name="fetch_home_every">Fetch Home every</string>
<string name="type_of_home_delay_title">Home fetch time</string>
<string name="set_fetch_home">Automatically fetch home messages</string>
<string name="home_cache">Home cache</string>
<string name="fetch_home_messages">Fetch home messages</string>
<string name="auto_fetch_missing">Automatically fetch missing messages</string>
<string name="set_mention_at_top">Mentions at the top</string>
<string name="set_mention_at_top_indication">When replying mentions will all be added to the beginning of the message</string>
</resources> </resources>

View file

@ -13,6 +13,14 @@
app:singleLineTitle="false" app:singleLineTitle="false"
app:summary="@string/set_capitalize_indication" app:summary="@string/set_capitalize_indication"
app:title="@string/set_capitalize" /> app:title="@string/set_capitalize" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="@string/SET_MENTIONS_AT_TOP"
app:singleLineTitle="false"
app:summary="@string/set_mention_at_top_indication"
app:title="@string/set_mention_at_top" />
<!-- <!--
<SwitchPreferenceCompat <SwitchPreferenceCompat
app:defaultValue="false" app:defaultValue="false"

View file

@ -4,7 +4,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<SwitchPreferenceCompat <SwitchPreference
app:defaultValue="false" app:defaultValue="false"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="@string/SET_FETCH_HOME" app:key="@string/SET_FETCH_HOME"

View file

@ -7,6 +7,12 @@
app:key="@string/SET_REMEMBER_POSITION" app:key="@string/SET_REMEMBER_POSITION"
app:singleLineTitle="false" app:singleLineTitle="false"
app:title="@string/remember_position" /> app:title="@string/remember_position" />
<SwitchPreferenceCompat
android:defaultValue="false"
app:iconSpaceReserved="false"
app:key="@string/SET_AUTO_FETCH_MISSING_MESSAGES"
app:singleLineTitle="false"
app:title="@string/auto_fetch_missing" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"

View file

@ -194,6 +194,11 @@ public class SparkButton extends FrameLayout implements View.OnClickListener {
public void setImageSize(@Px int imageSize) { public void setImageSize(@Px int imageSize) {
this.imageSize = imageSize; this.imageSize = imageSize;
if (imageView != null) {
imageView.getLayoutParams().width = imageSize;
imageView.getLayoutParams().height = imageSize;
imageView.requestLayout();
}
} }
public @ColorInt public @ColorInt

View file

@ -0,0 +1,7 @@
Added:
- Cache home in background (default disabled -> New settings category and per account) / change frequency
- Auto-fetch missing messages for the Home (default disabled -> in Settings - Timelines)
- Automatically switch between tabs when searching
Fixed:
- Some crashes

View file

@ -0,0 +1,17 @@
Added:
- Peertube 2FA support
- Cache home in background (default disabled -> New settings category and per account) / change frequency
- Auto-fetch missing messages for the Home (default disabled -> in Settings - Timelines)
- Automatically switch between tabs when searching
- More deep links detection
- Allow to group mentions at the top (default: disabled)
Fixed:
- Dynamic color for Android 12+
- Missing media description for previews
- Fix a crash when replying
- Fix button size not changed
- Forward tags in replies
- Media cannot be downloaded or shared with Android 10
- Some crashes

View file

@ -0,0 +1,2 @@
- Виправлено деякі помилки
- Дозволити поділитися зі застосунком

View file

@ -0,0 +1,20 @@
Додано:
- Налаштування експорту
- Поширення ручного переупорядкування списків на шкалі часу в підменю "Списки"
- Дозволено змінювати розподільник push-розсилок у налаштуваннях
Виправлено:
- Покращено попередній перегляд зображень
- Покращено сповіщення
- Медіа профілю відображаються у сітці
Виправлено:
- Не працюють деякі відео з Peertube
- Поважати обліковий запис видимості за замовчуванням під час відповіді
- Відрізняти gif від зображень
- Застосунок аварійно завершує роботу під час відкриття зовнішнього екземпляра на шкалі часу
- Кнопка "Видалити" у композиторі потоку призводить до аварійного завершення роботи програми
- Кнопка "Назад" відкриває багато старих активностей перед закриттям застосунку
- Проблеми зі спільним доступом
- Переупорядкування списків з проблемою інтерфейсу при зміні видимості
- Посилання неправильно відображається у повідомленнях з Friendica

View file

@ -0,0 +1,15 @@
Додано:
- Зміна іконки застосунку (Налаштування > Інтерфейс)
- Дозволено відключати "запам'ятовувати позицію" в хронологіях
- Дозволено відключати агрегацію сповіщень у налаштуваннях
Змінено:
- Дозволено вимикати/вмикати медіа для сповіщень
Виправлено:
- Допис втрачав "спойлер" при додаванні медіа
- Камера не працює на Android 11
- Агрегація сповіщень
- Вібрація при отриманні нових сповіщень
- Виправлено проблему з хронологією медіа
- Деякі збої в роботі

View file

@ -0,0 +1,22 @@
Додано:
- Редагування повідомлень (якщо ваш екземпляр підтримує цю функцію)
- Закріплення/відкріплення повідомлень
- Встановлення мови за замовчуванням для перекладів
- Зміна іконки застосунку (Налаштування > Інтерфейс)
- Дозволити вимкнути "запам'ятовувати позицію" на часових шкалах
- Дозволити вимкнути агрегацію сповіщень у налаштуваннях
- Іконка на прев'ю медіа за наявності опису
Змінено:
- Дозволено вимикати/вмикати медіа для сповіщень
Виправлено:
- Допис втрачав "спойлер" при додаванні медіа
- Камера не працює на Android 11
- Агрегація сповіщень
- Вібрація при отриманні нових сповіщень
- Виправлено проблему зі шкалою часу для медіа
- Виправлено деякі проблеми з темами
- Вирішено проблему з вбудованим браузером та openId
- Погана поведінка з Хронологією Артів
- Деякі збої

View file

@ -0,0 +1,13 @@
Додано:
- Відображення клієнта в детальних повідомленнях
- Візуальна підтримка лапок, що починаються з ">"
- Збільшення відступів для потоків (від нуля до 20, за замовчуванням 5)
- Видимість публічних відповідей встановлено на unlisted (можна вимкнути)
Змінено:
- Зменшено розмір заголовка при збільшенні розміру тексту
Виправлено:
- Фільтри не застосовуються
- Блокування акаунта не видаляє повідомлення у кеші
- Виправлено деякі збої

View file

@ -0,0 +1,14 @@
Додано:
- Підтримка відкриття посилань, що містять у своєму шляху /@display_name/ (працює на старих пристроях)
- Відображення кількості відповідей при увімкнених лічильниках
- Додано підтримку фільтрації повідомлень профілю
Змінено:
- Складання подання займає всю ширину навіть у потоках
- Скинуто маркер push-сповіщення при очищенні кешу
Виправлено:
- Чернетка зберігається при відповіді "ні" або діалоговому запиті без змін
- Фільтри не працюють з теґами
- Додано спеціальне повідомлення про помилку для теґів, за якими слідкують
- Порожні сторінки під час запуску застосунку

View file

@ -0,0 +1,14 @@
Додано:
- Повна підтримка нових фільтрів для Mastodon 4
- Відвідувати профілі без авторизації / Дозволити відображати всі їхні повідомлення
Змінено:
- Компонування перегляду займає всю ширину навіть у потоках
- Облікові записи можна вимкнути за таймером з їхнього профілю
Виправлено:
- Чернетка зберігається при відповіді "ні" або діалоговому запиті без змін
- Порожні сторінки під час запуску застосунку
- На деяких пристроях не вдається зберігати та ділитися медіа на деяких пристроях
- Додано підтримку сповіщень адміністратора
- Копіювання вмісту повідомлення

View file

@ -0,0 +1,13 @@
Додано:
- Список заблокованих доменів (дозволити розблокувати)
- Підтримати посилання gemini
- Запропоновані підписники
Змінено:
- Дозволено редагування пошукового запиту
Виправлено:
- Чернетки видалялися без попередження
- Застосунок вилітає, коли встановлено проксі
- Фільтр не синхронізується після редагування
- Деякі збої

View file

@ -0,0 +1,18 @@
Додано:
- Список заблокованих доменів (дозволити розблокувати)
- Підтримати посилання gemini
- Запропоновані підписники
- Мод/Адмін: Керування екземплярами заблокованих доменів
- Відкривати повідомлення з іншим обліковим записом
- Дозволити відключити сповіщення для адміністраторів
- Сортування списків
Змінено:
- Дозволено редагування пошукового запиту
Виправлено:
- Чернетки видалялися без попередження
- Видалено списки з "Керування термінами"
- Застосунок вилітає, коли встановлено проксі
- Фільтр не синхронізується після редагування
- Деякі збої / покращення

View file

@ -0,0 +1,33 @@
Додано:
- Публікуйте випадкові цитати
- Групові репости на домашній шкалі часу
- Перейменування часових шкал Nitter
- Підтримка Android 13
- Нумерація з пошуком / трендом
- Дозволити видалення лівого поля в повідомленнях (за замовчуванням: вимкнено)
Змінено:
- Відображати кнопку перекладу лише тоді, коли мова відрізняється
- Повага до пробілів між словами у повідомленнях
- Кнопка фокусування стала доступнішою під час редагування медіа
- Візуальний зворотний зв'язок для блокування у списку облікових записів
- Візуальні зміни за допомогою композиції / верхньої панелі
- Використовуйте власну назву часової шкали Nitter для керування часовими шкалами
Виправлено:
- Поведінка з перемикачем cw
- Урізані посилання на gimini
- Кнопки навігації не видно з медіа (Світла тема)
- Рядок стану з Android 5
- Виправлено посилання, на які не можна натиснути
- Виправлено глибокі посилання
- Виправлення віддалених потоків, які не відображаються у деяких випадках
- Додавання опису до спільних медіафайлів
- Відкриття з іншими обліковими записами
- Розмір символів не дотримується для Android 5-6
- Неправильний екземпляр отримано для instances.social
- Стрибає шкала часу при оновленні
- Посилання на згадки, теґи, URL-адреси не відображаються.
- Кастомні звуки каналів не застосовуються
- Користувачі з коротким іменем користувача не пов'язані між собою
- Виправлено збої

View file

@ -0,0 +1,21 @@
Додано:
- Додано підтримку бульбашкової шкали часу в додаткових функціях з фільтрами
- Дозволено відображати публічні профілі за замовчуванням для отримання всіх повідомлень (Налаштування > Інтерфейс)
- Виправлено помилку: Дозволено публікувати повідомлення локально (можна вимкнути у Налаштуваннях)
- Pixelfed: Спеціальний макет для повного відображення медіа (також працює в інших застосунках, де є медіа)
- Дозволено вирівнювати ліві кнопки дій у повідомленнях
Змінено:
- Повністю перероблено посилання у повідомленнях (також згадки та теґи)
- Додано закріплений теґ у "будь-який", щоб уникнути його втрати під час перейменування часової шкали
Виправлено:
- Посилання на повідомлення, які не обробляються застосунком
- CW при редагуванні повідомлення
- Виправлено push-сповіщення з кількома обліковими записами
- Нові повідомлення або сповіщення про редагування не надсилаються
- Виправлено цитати з теґами/згадками
- Виправлено сповіщення
- Виправлено надсилання кількох медіа
- Виправлено збої в роботі

View file

@ -1,16 +1,12 @@
It supports: Він підтримує:
- Mastodon, Pleroma, Pixelfed, Peertube, GNU Social, Friendica. - Mastodon, Pleroma, Pixelfed, Peertube, GNU Social, Friendica.
Застосунок має розширені особливості:
The app has advanced features (especially for Pleroma and Mastodon): - Складання потоків
- Підтримка мультиоблікових записів
- Multi-accounts support - Запланувати повідомлення від пристрою
- Schedule messages from the device - Відстежуйте та взаємодійте з віддаленими екземплярами
- Schedule boosts - Дії між обліковими записами за допомогою довгого натискання
- Bookmark messages - Функція перекладу
- Follow and interact with remote instances - Хронологія Артів
- Timed mute accounts
- Cross-account actions with a long press
- Translation feature
- Art timelines
- Video timelines