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 {
minSdk 21
targetSdk 33
versionCode 475
versionName "3.16.3"
versionCode 477
versionName "3.17.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
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",
"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
*/
private void scrollToTop() {
int position = binding.tabLayout.getSelectedTabPosition();
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) {
FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment);
fragmentMastodonTimeline.scrollToTop();

View file

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

View file

@ -250,7 +250,7 @@ public class MediaActivity extends BaseTransparentActivity implements OnDownload
int position = binding.mediaViewpager.getCurrentItem();
Attachment attachment = attachments.get(position);
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) {
ActivityCompat.requestPermissions(MediaActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE);
} else {

View file

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

View file

@ -44,6 +44,7 @@ import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityReorderTabsBinding;
import app.fedilab.android.databinding.PopupSearchInstanceBinding;
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.position = pinned.pinnedTimelines.size();
pinned.pinnedTimelines.add(pinnedTimeline);
if (pinned.user_id == null || pinned.instance == null) {
pinned.user_id = MainActivity.currentUserID;
pinned.instance = MainActivity.currentInstance;
}
if (update) {
try {
new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned);

View file

@ -318,4 +318,18 @@ public interface MastodonStatusesService {
@Header("Authorization") String token,
@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 app.fedilab.android.mastodon.helper.SpannableHelper;
import de.timfreiheit.mathjax.android.MathJaxView;
public class Status implements Serializable, Cloneable {
@ -108,6 +109,8 @@ public class Status implements Serializable, Cloneable {
public boolean cached = false;
@SerializedName("is_maths")
public Boolean isMaths;
@SerializedName("reactions")
public List<Reaction> reactions;
public Attachment art_attachment;
public boolean isExpended = false;
@ -128,6 +131,7 @@ public class Status implements Serializable, Cloneable {
public transient Spannable contentSpan;
public transient Spannable contentSpoilerSpan;
public transient Spannable contentTranslateSpan;
public transient MathJaxView mathJaxView;
@Override
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.");
}
ContentValues values = new ContentValues();
values.put(Sqlite.COL_INSTANCE, BaseMainActivity.currentInstance);
values.put(Sqlite.COL_USER_ID, BaseMainActivity.currentUserID);
values.put(Sqlite.COL_INSTANCE, bottomMenu.instance);
values.put(Sqlite.COL_USER_ID, bottomMenu.user_id);
values.put(Sqlite.COL_BOTTOM_MENU, menuItemListToStringStorage(bottomMenu.bottom_menu));
//Inserts bottom
try {

View file

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

View file

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

View file

@ -514,6 +514,8 @@ public class SpannableHelper {
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]+)?$");
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.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@ -567,6 +569,38 @@ public class SpannableHelper {
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
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);

View file

@ -265,7 +265,7 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
.setClearViewsEnabled(true)
.setTransparencyEnabled(true)
.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) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(EditImageActivity.this,

View file

@ -76,12 +76,13 @@ public class FetchHomeWorker extends Worker {
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);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
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)
.addTag(Helper.WORKER_REFRESH_NOTIFICATION)
PeriodicWorkRequest notificationPeriodic = new PeriodicWorkRequest.Builder(FetchHomeWorker.class, Long.parseLong(value), TimeUnit.MINUTES)
.setInputData(inputData)
.addTag(Helper.WORKER_REFRESH_HOME + account.user_id + account.instance)
.build();
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
public ListenableFuture<ForegroundInfo> getForegroundInfoAsync() {
if (Build.VERSION.SDK_INT >= 26) {
String channelName = "Notifications";
String channelDescription = "Fetched notifications";
NotificationChannel notifChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
notifChannel.setDescription(channelDescription);
notifChannel.setSound(null, null);
notifChannel.setShowBadge(false);
notificationManager.createNotificationChannel(notifChannel);
if (notificationManager.getNotificationChannel("notifications") != null) {
notificationManager.deleteNotificationChannel("notifications");
}
String channelName = "Fetch Home";
String channelDescription = "Fetch home messages";
NotificationChannel fetchHomeChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
fetchHomeChannel.setDescription(channelDescription);
fetchHomeChannel.setSound(null, null);
fetchHomeChannel.setShowBadge(false);
notificationManager.createNotificationChannel(fetchHomeChannel);
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
notificationBuilder.setSmallIcon(R.drawable.ic_notification)
@ -115,20 +112,20 @@ public class FetchHomeWorker extends Worker {
@NonNull
private ForegroundInfo createForegroundInfo() {
if (Build.VERSION.SDK_INT >= 26) {
String channelName = "Notifications";
String channelDescription = "Fetched notifications";
NotificationChannel notifChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
notifChannel.setSound(null, null);
notifChannel.setShowBadge(false);
notifChannel.setDescription(channelDescription);
notificationManager.createNotificationChannel(notifChannel);
String channelName = "Fetch Home";
String channelDescription = "Fetch home messages";
NotificationChannel fetchHomeChannel = new NotificationChannel(CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW);
fetchHomeChannel.setSound(null, null);
fetchHomeChannel.setShowBadge(false);
fetchHomeChannel.setDescription(channelDescription);
notificationManager.createNotificationChannel(fetchHomeChannel);
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID);
notificationBuilder.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.ic_launcher_foreground))
.setContentTitle(getApplicationContext().getString(R.string.notifications))
.setContentText(getApplicationContext().getString(R.string.fetch_notifications))
.setContentTitle(getApplicationContext().getString(R.string.fetch_home_messages))
.setContentText(getApplicationContext().getString(R.string.set_fetch_home))
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setSilent(true)
.setPriority(Notification.PRIORITY_LOW);
@ -138,10 +135,15 @@ public class FetchHomeWorker extends Worker {
@NonNull
@Override
public Result doWork() {
setForegroundAsync(createForegroundInfo());
String instance = getInputData().getString(Helper.ARG_INSTANCE);
String userId = getInputData().getString(Helper.ARG_USER_ID);
try {
List<BaseAccount> accounts = new Account(getApplicationContext()).getCrossAccounts();
for (BaseAccount account : accounts) {
BaseAccount account = new Account(getApplicationContext()).getUniqAccount(userId, instance);
if (account != null) {
try {
fetchHome(getApplicationContext(), account);
} catch (IOException e) {
@ -168,7 +170,7 @@ public class FetchHomeWorker extends Worker {
String max_id = null;
MastodonTimelinesService mastodonTimelinesService = init(account.instance);
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) {
Response<List<Status>> homeResponse = homeCall.execute();
if (homeResponse.isSuccessful()) {
@ -183,12 +185,7 @@ public class FetchHomeWorker extends Worker {
statusCache.type = Timeline.TimeLineEnum.HOME;
statusCache.status_id = status.id;
try {
int inserted = statusCacheDAO.insertOrUpdate(statusCache, Timeline.TimeLineEnum.HOME.getValue());
//We reached already cached messages
if (inserted == 0) {
canContinue = false;
break;
}
statusCacheDAO.insertOrUpdate(statusCache, Timeline.TimeLineEnum.HOME.getValue());
} catch (DBException e) {
e.printStackTrace();
}
@ -206,9 +203,14 @@ public class FetchHomeWorker extends Worker {
canContinue = false;
}
}
//Pause between calls (1 second)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
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.helper.CacheHelper;
import app.fedilab.android.mastodon.helper.MastodonHelper;
import app.fedilab.android.peertube.helper.Helper;
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) {
CacheAccount cacheAccount = accountList.get(position);
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));
holder.binding.displayName.setText(cacheAccount.account.mastodon_account.display_name);
if (cacheAccount.account.mastodon_account != null) {
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 -> {
if (countStatuses != null && countStatuses.size() == 3) {
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.instanceInfo;
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.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.viewmodel.mastodon.AccountsVM;
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;
@ -311,8 +314,10 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
//Put other accounts mentioned at the bottom
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 (capitalize) {
if (capitalize && !mentionsAtTop) {
statusDraft.text = inReplyToUser.acct.startsWith("@") ? inReplyToUser.acct + "\n" : "@" + inReplyToUser.acct + "\n";
} else {
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);
statusDraft.cursorPosition = statusDraft.text.length();
if (statusDraft.mentions.size() > 1) {
statusDraft.text += "\n";
if (!mentionsAtTop) {
statusDraft.text += "\n";
}
for (int i = 1; i < statusDraft.mentions.size(); i++) {
String tootTemp = String.format("@%s ", statusDraft.mentions.get(i).acct);
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
*/
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) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions((Activity) context,
@ -702,19 +709,26 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
}
Matcher mathsPatterns = Helper.mathsComposePattern.matcher((s.toString()));
if (mathsPatterns.find()) {
if (holder.binding.laTexViewContainer.getVisibility() != View.VISIBLE) {
holder.binding.laTexViewContainer.setVisibility(View.VISIBLE);
if (holder.binding.laTexViewContainer.getChildCount() == 0) {
MathJaxConfig mathJaxConfig = new MathJaxConfig();
mathJaxConfig.setAutomaticLinebreaks(true);
mathJaxConfig.setInput(TeX);
switch (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) {
case Configuration.UI_MODE_NIGHT_YES:
holder.binding.laTexView.setTextColor("white");
mathJaxConfig.setTextColor("white");
break;
case Configuration.UI_MODE_NIGHT_NO:
holder.binding.laTexView.setTextColor("black");
mathJaxConfig.setTextColor("black");
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 {
holder.binding.laTexViewContainer.setVisibility(View.GONE);
}
@ -1297,6 +1311,8 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
ComposeViewHolder holder = (ComposeViewHolder) viewHolder;
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 forwardTag = sharedpreferences.getBoolean(context.getString(R.string.SET_FORWARD_TAGS_IN_REPLY), true);
if (mathsComposer) {
holder.binding.buttonMathsComposer.setVisibility(View.VISIBLE);
@ -1565,13 +1581,48 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
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()) {
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) {
statusDraft.setCursorToEnd = false;
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) {
holder.binding.contentSpoiler.setText(statusDraft.spoiler_text);
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.ThemeHelper;
import app.fedilab.android.mastodon.viewmodel.mastodon.AnnouncementsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.StatusesVM;
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 String announcementId;
private final boolean statusReaction;
private final boolean isPleroma;
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) {
this.reactions = reactions;
this.announcementId = announcementId;
this.statusReaction = statusReaction;
this.isPleroma = true;
}
ReactionAdapter(String announcementId, List<Reaction> reactions) {
this.reactions = reactions;
this.announcementId = announcementId;
this.statusReaction = false;
this.isPleroma = true;
}
@NonNull
@ -101,7 +113,7 @@ public class ReactionAdapter extends RecyclerView.Adapter<ReactionAdapter.Reacti
}
notifyItemChanged(position);
});
} else {
} else if (isPleroma) {
ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
holder.binding.reactionContainer.setOnClickListener(v -> {
if (reaction.me) {
@ -115,6 +127,20 @@ public class ReactionAdapter extends RecyclerView.Adapter<ReactionAdapter.Reacti
}
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 app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.DrawerReorderBinding;
import app.fedilab.android.mastodon.activities.ReorderTimelinesActivity;
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 -> {
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) {
holder.binding.hide.setImageResource(R.drawable.ic_baseline_visibility_24);
} else {

View file

@ -50,6 +50,7 @@ import android.os.Looper;
import android.text.Html;
import android.text.SpannableString;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
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 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 autofetch = sharedpreferences.getBoolean(context.getString(R.string.SET_AUTO_FETCH_MISSING_MESSAGES), false);
if (compactButtons) {
ConstraintSet set = new ConstraintSet();
@ -517,7 +520,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
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) {
holder.binding.layoutReactions.getRoot().setVisibility(View.VISIBLE);
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
= new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
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 {
holder.binding.layoutReactions.getRoot().setVisibility(View.GONE);
holder.binding.layoutReactions.reactionsView.setAdapter(null);
@ -537,33 +547,57 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}).setOnEmojiClickListener((emoji, imageView) -> {
String emojiStr = imageView.getUnicode();
boolean alreadyAdded = false;
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) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
if (status.pleroma != null && status.pleroma.emoji_reactions != null) {
for (Reaction reaction : status.pleroma.emoji_reactions) {
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
}
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
}
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
}
}
if (!alreadyAdded) {
Reaction reaction = new Reaction();
reaction.me = true;
reaction.count = 1;
reaction.name = emojiStr;
status.pleroma.emoji_reactions.add(0, reaction);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}
ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
if (alreadyAdded) {
actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
if (!alreadyAdded) {
Reaction reaction = new Reaction();
reaction.me = true;
reaction.count = 1;
reaction.name = emojiStr;
status.pleroma.emoji_reactions.add(0, reaction);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}
ActionsVM actionVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
if (alreadyAdded) {
actionVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
actionVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
}
} 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);
@ -589,32 +623,61 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
String url = emojis.get(BaseMainActivity.currentInstance).get(index).url;
String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url;
boolean alreadyAdded = false;
for (Reaction reaction : status.pleroma.emoji_reactions) {
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
if (status.pleroma != null && status.pleroma.emoji_reactions != null) {
for (Reaction reaction : status.pleroma.emoji_reactions) {
if (reaction.name.compareTo(emojiStr) == 0 && reaction.me) {
alreadyAdded = true;
reaction.count = (reaction.count - 1);
if (reaction.count == 0) {
status.pleroma.emoji_reactions.remove(reaction);
}
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
break;
}
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.pleroma.emoji_reactions.add(0, reaction);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}
ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
if (alreadyAdded) {
actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
if (!alreadyAdded) {
Reaction reaction = new Reaction();
reaction.me = true;
reaction.count = 1;
reaction.name = emojiStr;
reaction.url = url;
reaction.static_url = static_url;
status.pleroma.emoji_reactions.add(0, reaction);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
}
ActionsVM actionsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(ActionsVM.class);
if (alreadyAdded) {
actionsVM.removeReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
} else {
actionsVM.addReaction(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id, emojiStr);
}
} 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);
@ -1067,7 +1130,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
//Button sizes depending of the defined scale
float normalSize = Helper.convertDpToPixel(28, context);
holder.binding.actionButtonReply.getLayoutParams().width = (int) (normalSize * scaleIcon);
holder.binding.actionButtonReply.getLayoutParams().height = (int) (normalSize * scaleIcon);
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.actionButtonBookmark.setImageSize((int) (normalSize * scaleIcon));
holder.binding.statusAddCustomEmoji.getLayoutParams().width = (int) (normalSize * scaleIcon);
holder.binding.statusAddCustomEmoji.getLayoutParams().height = (int) (normalSize * scaleIcon);
holder.binding.statusAddCustomEmoji.requestLayout();
@ -2095,44 +2158,65 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
}
if (status.isFetchMore && fetchMoreCallBack != null) {
DrawerFetchMoreBinding drawerFetchMoreBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(context));
if (status.positionFetchMore == Status.PositionFetchMore.BOTTOM) {
holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
holder.binding.fetchMoreContainerTop.setVisibility(View.VISIBLE);
holder.binding.fetchMoreContainerTop.removeAllViews();
holder.binding.fetchMoreContainerTop.addView(drawerFetchMoreBinding.getRoot());
} else {
holder.binding.fetchMoreContainerBottom.setVisibility(View.VISIBLE);
holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
holder.binding.fetchMoreContainerBottom.removeAllViews();
holder.binding.fetchMoreContainerBottom.addView(drawerFetchMoreBinding.getRoot());
}
drawerFetchMoreBinding.fetchMoreMin.setOnClickListener(v -> {
status.isFetchMore = false;
int position = holder.getBindingAdapterPosition();
adapter.notifyItemChanged(position);
if (position < statusList.size() - 1) {
if (!autofetch) {
DrawerFetchMoreBinding drawerFetchMoreBinding = DrawerFetchMoreBinding.inflate(LayoutInflater.from(context));
if (status.positionFetchMore == Status.PositionFetchMore.BOTTOM) {
holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
holder.binding.fetchMoreContainerTop.setVisibility(View.VISIBLE);
holder.binding.fetchMoreContainerTop.removeAllViews();
holder.binding.fetchMoreContainerTop.addView(drawerFetchMoreBinding.getRoot());
} else {
holder.binding.fetchMoreContainerBottom.setVisibility(View.VISIBLE);
holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
holder.binding.fetchMoreContainerBottom.removeAllViews();
holder.binding.fetchMoreContainerBottom.addView(drawerFetchMoreBinding.getRoot());
}
drawerFetchMoreBinding.fetchMoreMin.setOnClickListener(v -> {
status.isFetchMore = false;
int position = holder.getBindingAdapterPosition();
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;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(position + 1).id;
fromId = statusList.get(holder.getBindingAdapterPosition()).id;
} else {
fromId = status.id;
fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
}
fetchMoreCallBack.onClickMinId(fromId, status);
}
});
drawerFetchMoreBinding.fetchMoreMax.setOnClickListener(v -> {
//We hide the button
fetchMoreCallBack.onClickMaxId(fromId, status);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
});
} else {
holder.binding.fetchMoreContainerBottom.setVisibility(View.GONE);
holder.binding.fetchMoreContainerTop.setVisibility(View.GONE);
status.isFetchMore = false;
String fromId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(holder.getBindingAdapterPosition()).id;
} else {
fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
int position = holder.getBindingAdapterPosition();
String statusIdMin = null, statusIdMax;
if (position < statusList.size() - 1) {
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
statusIdMin = statusList.get(position + 1).id;
} else {
statusIdMin = status.id;
}
}
fetchMoreCallBack.onClickMaxId(fromId, status);
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
});
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
statusIdMax = statusList.get(holder.getBindingAdapterPosition()).id;
} else {
statusIdMax = statusList.get(holder.getBindingAdapterPosition() - 1).id;
}
fetchMoreCallBack.autoFetch(statusIdMin, statusIdMax, status);
}
} else {
holder.binding.fetchMoreContainerBottom.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) {
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;
if (attachment.url == null) {
finalUrl = attachment.remote_url;
@ -2590,32 +2677,53 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.bindingFilteredHide.dividerCard.setVisibility(View.GONE);
}
if (status.isFetchMore && fetchMoreCallBack != null) {
holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE);
holder.bindingFilteredHide.layoutFetchMore.fetchMoreMin.setOnClickListener(v -> {
status.isFetchMore = false;
notifyItemChanged(holder.getBindingAdapterPosition());
if (holder.getBindingAdapterPosition() < statusList.size() - 1) {
boolean autofetch = sharedpreferences.getBoolean(context.getString(R.string.SET_AUTO_FETCH_MISSING_MESSAGES), false);
if (!autofetch) {
holder.bindingFilteredHide.layoutFetchMore.fetchMoreContainer.setVisibility(View.VISIBLE);
holder.bindingFilteredHide.layoutFetchMore.fetchMoreMin.setOnClickListener(v -> {
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;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(holder.getBindingAdapterPosition() + 1).id;
fromId = statusList.get(holder.getBindingAdapterPosition()).id;
} else {
fromId = status.id;
fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
}
fetchMoreCallBack.onClickMinId(fromId, status);
}
});
holder.bindingFilteredHide.layoutFetchMore.fetchMoreMax.setOnClickListener(v -> {
//We hide the button
fetchMoreCallBack.onClickMaxId(fromId, status);
});
} else {
status.isFetchMore = false;
String fromId;
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
fromId = statusList.get(holder.getBindingAdapterPosition()).id;
} else {
fromId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
String minId = null, maxId;
if (holder.getBindingAdapterPosition() < statusList.size() - 1) {
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
minId = statusList.get(holder.getBindingAdapterPosition() + 1).id;
} else {
minId = status.id;
}
}
fetchMoreCallBack.onClickMaxId(fromId, status);
notifyItemChanged(holder.getBindingAdapterPosition());
});
if (status.positionFetchMore == Status.PositionFetchMore.TOP) {
maxId = statusList.get(holder.getBindingAdapterPosition()).id;
} else {
maxId = statusList.get(holder.getBindingAdapterPosition() - 1).id;
}
fetchMoreCallBack.autoFetch(minId, maxId, status);
}
} else {
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 onClickMaxId(String max_id, Status statusToUpdate);
void autoFetch(String min_id, String max_id, Status status);
}
public static class StatusViewHolder extends RecyclerView.ViewHolder {

View file

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

View file

@ -154,7 +154,7 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat {
Preference pref_export_settings = findPreference(getString(R.string.pref_export_settings));
if (pref_export_settings != null) {
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);
} else {
try {

View file

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

View file

@ -30,6 +30,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
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.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Accounts;
@ -1292,4 +1293,48 @@ public class StatusesVM extends AndroidViewModel {
}).start();
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) {
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) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, app.fedilab.android.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE);
} else {

View file

@ -93,6 +93,15 @@ public class Sqlite extends SQLiteOpenHelper {
public static final String COL_ABOUT = "ABOUT";
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 + " ("
+ COL_USER_ID + " TEXT NOT NULL, "
+ COL_INSTANCE + " TEXT NOT NULL, "
@ -192,8 +201,8 @@ public class Sqlite extends SQLiteOpenHelper {
+ COL_USER_ID + " TEXT NOT NULL, "
+ COL_TYPE + " TEXT NOT NULL, "
+ COL_MUTED_ACCOUNTS + " TEXT)";
public static SQLiteDatabase db;
private static Sqlite sInstance;
private final String CREATE_TABLE_STORED_INSTANCES = "CREATE TABLE "
+ TABLE_BOOKMARKED_INSTANCES + "("
+ COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@ -202,6 +211,11 @@ public class Sqlite extends SQLiteOpenHelper {
+ COL_ABOUT + " 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) {
super(context, name, factory, version);
}

View file

@ -237,7 +237,7 @@ public class FragmentLoginMain extends Fragment {
}
});
} 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);
} else {
proceed();

View file

@ -2,10 +2,10 @@
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:tint="?attr/colorControlNormal"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
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>

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

View file

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

View file

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

View file

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

View file

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

View file

@ -41,14 +41,6 @@
app:layout_constraintStart_toStartOf="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>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<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>

View file

@ -999,4 +999,11 @@
<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="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>

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="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="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>

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="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="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>

View file

@ -906,4 +906,6 @@
<string name="set_maths_support">Escrever fórmula</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="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>

View file

@ -977,4 +977,14 @@
<string name="filter_languages">Filtra is limbas</string>
<string name="translate_in">Borta in</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>

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="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="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>

View file

@ -581,4 +581,21 @@
<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="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>

View file

@ -854,7 +854,7 @@
<string name="toast_unpin">消息不再置顶!</string>
<string name="toast_pin">消息已置顶</string>
<string name="staff">职员</string>
<string name="approved">得到正式认可的</string>
<string name="approved">已批准</string>
<string name="approve">批准</string>
<string name="set_unlisted_replies">不公开回复</string>
<string name="max_indentation_thread">同主题帖子中最大缩进</string>
@ -987,4 +987,11 @@
<string name="toast_error_peertube_not_supported">您的 Peertube 太旧,应用程序不支持。</string>
<string name="customize_timelines">自定义时间线</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>

View file

@ -1390,6 +1390,8 @@
<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_MENTIONS_AT_TOP" translatable="false">SET_MENTIONS_AT_TOP</string>
<string name="SET_THEME_BASE" translatable="false">SET_THEME_BASE</string>
<string name="SET_DYNAMICCOLOR" translatable="false">SET_DYNAMICCOLOR</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_NOTIF_SILENT" translatable="false">SET_NOTIF_SILENT</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_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>
@ -2236,4 +2239,14 @@
<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="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>

View file

@ -13,6 +13,14 @@
app:singleLineTitle="false"
app:summary="@string/set_capitalize_indication"
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
app:defaultValue="false"

View file

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

View file

@ -7,6 +7,12 @@
app:key="@string/SET_REMEMBER_POSITION"
app:singleLineTitle="false"
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
android:defaultValue="false"
app:iconSpaceReserved="false"

View file

@ -194,6 +194,11 @@ public class SparkButton extends FrameLayout implements View.OnClickListener {
public void setImageSize(@Px int imageSize) {
this.imageSize = imageSize;
if (imageView != null) {
imageView.getLayoutParams().width = imageSize;
imageView.getLayoutParams().height = imageSize;
imageView.requestLayout();
}
}
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.
Застосунок має розширені особливості:
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
- Складання потоків
- Підтримка мультиоблікових записів
- Запланувати повідомлення від пристрою
- Відстежуйте та взаємодійте з віддаленими екземплярами
- Дії між обліковими записами за допомогою довгого натискання
- Функція перекладу
- Хронологія Артів