mirror of
https://codeberg.org/tom79/Fedilab.git
synced 2024-12-22 16:50:04 +02:00
Merge branch 'develop'
This commit is contained in:
commit
af10d647b9
28 changed files with 680 additions and 159 deletions
|
@ -13,8 +13,8 @@ android {
|
|||
defaultConfig {
|
||||
minSdk 21
|
||||
targetSdk 33
|
||||
versionCode 462
|
||||
versionName "3.14.0"
|
||||
versionCode 463
|
||||
versionName "3.14.1"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
flavorDimensions "default"
|
||||
|
@ -97,6 +97,10 @@ dependencies {
|
|||
implementation 'org.framagit.tom79:SparkButton:1.0.13'
|
||||
implementation "com.github.bumptech.glide:glide:4.14.2"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:4.14.2"
|
||||
implementation("com.github.bumptech.glide:recyclerview-integration:4.14.2") {
|
||||
// Excludes the support library because it's already included by Glide.
|
||||
transitive = false
|
||||
}
|
||||
|
||||
implementation "org.jsoup:jsoup:1.15.1"
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
[
|
||||
{
|
||||
"version": "3.14.1",
|
||||
"code": "463",
|
||||
"note": "Added:\n- Search bar: display suggestions when starting by \"@\" or \"#\"\n\nChanged:\n- Preload media in timelines to avoid jumps\n- Search: Automatically switch to account tab if no results for tags\n\nFixed:\n- Fix jumps with the fetch more feature\n- Fix videos cannot be saved\n- Tags cannot be pinned when there are no custom tabs\n- PixelFed view: NSFW not honored\n- Fix crashes"
|
||||
},
|
||||
{
|
||||
"version": "3.14.0",
|
||||
"code": "462",
|
||||
|
|
|
@ -23,11 +23,13 @@ import static app.fedilab.android.ui.drawer.StatusAdapter.sendAction;
|
|||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.SearchManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
|
@ -35,6 +37,7 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.BaseColumns;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextWatcher;
|
||||
|
@ -63,6 +66,7 @@ import androidx.appcompat.widget.SearchView;
|
|||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
@ -75,7 +79,9 @@ import androidx.preference.PreferenceManager;
|
|||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable;
|
||||
import com.bumptech.glide.request.FutureTarget;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
@ -91,6 +97,7 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -129,6 +136,7 @@ import app.fedilab.android.client.entities.api.Filter;
|
|||
import app.fedilab.android.client.entities.api.Instance;
|
||||
import app.fedilab.android.client.entities.api.MastodonList;
|
||||
import app.fedilab.android.client.entities.api.Status;
|
||||
import app.fedilab.android.client.entities.api.Tag;
|
||||
import app.fedilab.android.client.entities.app.Account;
|
||||
import app.fedilab.android.client.entities.app.BaseAccount;
|
||||
import app.fedilab.android.client.entities.app.BottomMenu;
|
||||
|
@ -146,12 +154,15 @@ import app.fedilab.android.helper.Helper;
|
|||
import app.fedilab.android.helper.MastodonHelper;
|
||||
import app.fedilab.android.helper.PinnedTimelineHelper;
|
||||
import app.fedilab.android.helper.PushHelper;
|
||||
import app.fedilab.android.ui.drawer.AccountsSearchTopBarAdapter;
|
||||
import app.fedilab.android.ui.drawer.TagSearchTopBarAdapter;
|
||||
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonConversation;
|
||||
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
|
||||
import app.fedilab.android.ui.fragment.timeline.FragmentNotificationContainer;
|
||||
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
|
||||
import app.fedilab.android.viewmodel.mastodon.FiltersVM;
|
||||
import app.fedilab.android.viewmodel.mastodon.InstancesVM;
|
||||
import app.fedilab.android.viewmodel.mastodon.SearchVM;
|
||||
import app.fedilab.android.viewmodel.mastodon.TimelinesVM;
|
||||
import app.fedilab.android.viewmodel.mastodon.TopBarVM;
|
||||
import es.dmoral.toasty.Toasty;
|
||||
|
@ -777,6 +788,75 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
|
|||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
String pattern = "^(@[\\w_-]+@[a-z0-9.\\-]+|@[\\w_-]+)";
|
||||
final Pattern mentionPattern = Pattern.compile(pattern);
|
||||
String patternTag = "^#([\\w-]{2,})$";
|
||||
final Pattern tagPattern = Pattern.compile(patternTag);
|
||||
Matcher matcherMention, matcherTag;
|
||||
matcherMention = mentionPattern.matcher(newText);
|
||||
matcherTag = tagPattern.matcher(newText);
|
||||
if (newText.trim().isEmpty()) {
|
||||
binding.toolbarSearch.setSuggestionsAdapter(null);
|
||||
}
|
||||
if (matcherMention.matches()) {
|
||||
String[] from = new String[]{SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_TEXT_1};
|
||||
int[] to = new int[]{R.id.account_pp, R.id.account_un};
|
||||
String searchGroup = matcherMention.group();
|
||||
AccountsVM accountsVM = new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class);
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID,
|
||||
SearchManager.SUGGEST_COLUMN_ICON_1,
|
||||
SearchManager.SUGGEST_COLUMN_TEXT_1});
|
||||
accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, 5, false, false)
|
||||
.observe(BaseMainActivity.this, accounts -> {
|
||||
if (accounts == null) {
|
||||
return;
|
||||
}
|
||||
AccountsSearchTopBarAdapter cursorAdapter = new AccountsSearchTopBarAdapter(BaseMainActivity.this, accounts, R.layout.drawer_account_search, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
binding.toolbarSearch.setSuggestionsAdapter(cursorAdapter);
|
||||
new Thread(() -> {
|
||||
int i = 0;
|
||||
for (app.fedilab.android.client.entities.api.Account account : accounts) {
|
||||
FutureTarget<File> futureTarget = Glide
|
||||
.with(BaseMainActivity.this.getApplicationContext())
|
||||
.load(account.avatar_static)
|
||||
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
|
||||
File cacheFile;
|
||||
try {
|
||||
cacheFile = futureTarget.get();
|
||||
cursor.addRow(new String[]{String.valueOf(i), cacheFile.getAbsolutePath(), "@" + account.acct});
|
||||
i++;
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
runOnUiThread(() -> cursorAdapter.changeCursor(cursor));
|
||||
}).start();
|
||||
|
||||
});
|
||||
} else if (matcherTag.matches()) {
|
||||
SearchVM searchVM = new ViewModelProvider(BaseMainActivity.this).get(SearchVM.class);
|
||||
String[] from = new String[]{SearchManager.SUGGEST_COLUMN_TEXT_1};
|
||||
int[] to = new int[]{R.id.tag_name};
|
||||
String searchGroup = matcherTag.group();
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID,
|
||||
SearchManager.SUGGEST_COLUMN_TEXT_1});
|
||||
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, null,
|
||||
"hashtags", false, true, false, 0,
|
||||
null, null, 10).observe(BaseMainActivity.this,
|
||||
results -> {
|
||||
if (results == null || results.hashtags == null) {
|
||||
return;
|
||||
}
|
||||
TagSearchTopBarAdapter cursorAdapter = new TagSearchTopBarAdapter(BaseMainActivity.this, results.hashtags, R.layout.drawer_tag_search, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
binding.toolbarSearch.setSuggestionsAdapter(cursorAdapter);
|
||||
int i = 0;
|
||||
for (Tag tag : results.hashtags) {
|
||||
cursor.addRow(new String[]{String.valueOf(i), "#" + tag.name});
|
||||
i++;
|
||||
}
|
||||
runOnUiThread(() -> cursorAdapter.changeCursor(cursor));
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -105,8 +105,10 @@ public class HashTagActivity extends BaseActivity {
|
|||
});
|
||||
ReorderVM reorderVM = new ViewModelProvider(HashTagActivity.this).get(ReorderVM.class);
|
||||
reorderVM.getAllPinned().observe(HashTagActivity.this, pinned -> {
|
||||
if (pinned != null) {
|
||||
this.pinned = pinned;
|
||||
if (pinned == null) {
|
||||
pinned = new Pinned();
|
||||
pinned.pinnedTimelines = new ArrayList<>();
|
||||
}
|
||||
pinnedTag = false;
|
||||
if (pinned.pinnedTimelines != null) {
|
||||
for (PinnedTimeline pinnedTimeline : pinned.pinnedTimelines) {
|
||||
|
@ -120,7 +122,6 @@ public class HashTagActivity extends BaseActivity {
|
|||
}
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (MainActivity.filterFetched && MainActivity.mainFilters != null) {
|
||||
mutedTag = false;
|
||||
|
|
|
@ -16,7 +16,9 @@ package app.fedilab.android.activities;
|
|||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.database.MatrixCursor;
|
||||
import android.os.Bundle;
|
||||
import android.provider.BaseColumns;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
@ -26,22 +28,38 @@ import android.widget.Toast;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.cursoradapter.widget.CursorAdapter;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.FutureTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import app.fedilab.android.BaseMainActivity;
|
||||
import app.fedilab.android.R;
|
||||
import app.fedilab.android.client.entities.api.Tag;
|
||||
import app.fedilab.android.databinding.ActivitySearchResultTabsBinding;
|
||||
import app.fedilab.android.helper.Helper;
|
||||
import app.fedilab.android.ui.drawer.AccountsSearchTopBarAdapter;
|
||||
import app.fedilab.android.ui.drawer.TagSearchTopBarAdapter;
|
||||
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount;
|
||||
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTag;
|
||||
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
|
||||
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
|
||||
import app.fedilab.android.viewmodel.mastodon.SearchVM;
|
||||
import es.dmoral.toasty.Toasty;
|
||||
|
||||
|
||||
|
@ -140,6 +158,75 @@ public class SearchResultTabActivity extends BaseBarActivity {
|
|||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
String pattern = "^(@[\\w_-]+@[a-z0-9.\\-]+|@[\\w_-]+)";
|
||||
final Pattern mentionPattern = Pattern.compile(pattern);
|
||||
String patternTag = "^#([\\w-]{2,})$";
|
||||
final Pattern tagPattern = Pattern.compile(patternTag);
|
||||
Matcher matcherMention, matcherTag;
|
||||
matcherMention = mentionPattern.matcher(newText);
|
||||
matcherTag = tagPattern.matcher(newText);
|
||||
if (newText.trim().isEmpty()) {
|
||||
searchView.setSuggestionsAdapter(null);
|
||||
}
|
||||
if (matcherMention.matches()) {
|
||||
String[] from = new String[]{SearchManager.SUGGEST_COLUMN_ICON_1, SearchManager.SUGGEST_COLUMN_TEXT_1};
|
||||
int[] to = new int[]{R.id.account_pp, R.id.account_un};
|
||||
String searchGroup = matcherMention.group();
|
||||
AccountsVM accountsVM = new ViewModelProvider(SearchResultTabActivity.this).get(AccountsVM.class);
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID,
|
||||
SearchManager.SUGGEST_COLUMN_ICON_1,
|
||||
SearchManager.SUGGEST_COLUMN_TEXT_1});
|
||||
accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, 5, false, false)
|
||||
.observe(SearchResultTabActivity.this, accounts -> {
|
||||
if (accounts == null) {
|
||||
return;
|
||||
}
|
||||
AccountsSearchTopBarAdapter cursorAdapter = new AccountsSearchTopBarAdapter(SearchResultTabActivity.this, accounts, R.layout.drawer_account_search, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
searchView.setSuggestionsAdapter(cursorAdapter);
|
||||
new Thread(() -> {
|
||||
int i = 0;
|
||||
for (app.fedilab.android.client.entities.api.Account account : accounts) {
|
||||
FutureTarget<File> futureTarget = Glide
|
||||
.with(SearchResultTabActivity.this.getApplicationContext())
|
||||
.load(account.avatar_static)
|
||||
.downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
|
||||
File cacheFile;
|
||||
try {
|
||||
cacheFile = futureTarget.get();
|
||||
cursor.addRow(new String[]{String.valueOf(i), cacheFile.getAbsolutePath(), "@" + account.acct});
|
||||
i++;
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
runOnUiThread(() -> cursorAdapter.changeCursor(cursor));
|
||||
}).start();
|
||||
|
||||
});
|
||||
} else if (matcherTag.matches()) {
|
||||
SearchVM searchVM = new ViewModelProvider(SearchResultTabActivity.this).get(SearchVM.class);
|
||||
String[] from = new String[]{SearchManager.SUGGEST_COLUMN_TEXT_1};
|
||||
int[] to = new int[]{R.id.tag_name};
|
||||
String searchGroup = matcherTag.group();
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{BaseColumns._ID,
|
||||
SearchManager.SUGGEST_COLUMN_TEXT_1});
|
||||
searchVM.search(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, null,
|
||||
"hashtags", false, true, false, 0,
|
||||
null, null, 10).observe(SearchResultTabActivity.this,
|
||||
results -> {
|
||||
if (results == null || results.hashtags == null) {
|
||||
return;
|
||||
}
|
||||
TagSearchTopBarAdapter cursorAdapter = new TagSearchTopBarAdapter(SearchResultTabActivity.this, results.hashtags, R.layout.drawer_tag_search, null, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||
searchView.setSuggestionsAdapter(cursorAdapter);
|
||||
int i = 0;
|
||||
for (Tag tag : results.hashtags) {
|
||||
cursor.addRow(new String[]{String.valueOf(i), "#" + tag.name});
|
||||
i++;
|
||||
}
|
||||
runOnUiThread(() -> cursorAdapter.changeCursor(cursor));
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -188,6 +275,10 @@ public class SearchResultTabActivity extends BaseBarActivity {
|
|||
}
|
||||
|
||||
|
||||
public void moveToAccount() {
|
||||
binding.searchViewpager.setCurrentItem(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pager adapter for the 4 fragments
|
||||
*/
|
||||
|
|
|
@ -45,13 +45,14 @@ public class Attachment implements Serializable {
|
|||
public String local_path;
|
||||
@SerializedName("meta")
|
||||
public Meta meta;
|
||||
@SerializedName("sensitive")
|
||||
public boolean sensitive = false;
|
||||
|
||||
public String peertubeHost = null;
|
||||
public String peertubeId = null;
|
||||
public String focus = null;
|
||||
public String translation = null;
|
||||
|
||||
public float measuredWidth = -1.f;
|
||||
|
||||
public static class Meta implements Serializable {
|
||||
@SerializedName("focus")
|
||||
|
|
|
@ -522,9 +522,8 @@ public class Helper {
|
|||
long months = days / 30;
|
||||
long years = days / 365;
|
||||
|
||||
String format = DateFormat.getDateInstance(DateFormat.SHORT).format(date);
|
||||
if (years > 0) {
|
||||
return format;
|
||||
return DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(date);
|
||||
} else if (months > 0 || days > 7) {
|
||||
//Removes the year depending of the locale from DateFormat.SHORT format
|
||||
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
|
||||
|
|
|
@ -94,30 +94,14 @@ public class MediaHelper {
|
|||
try {
|
||||
request = new DownloadManager.Request(Uri.parse(url.trim()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
String mime = getMimeType(url);
|
||||
|
||||
final String fileName = URLUtil.guessFileName(url, null, null);
|
||||
request.allowScanningByMediaScanner();
|
||||
String myDir;
|
||||
if (mime.toLowerCase().startsWith("video")) {
|
||||
myDir = Environment.DIRECTORY_MOVIES + "/" + context.getString(R.string.app_name);
|
||||
} else if (mime.toLowerCase().startsWith("audio")) {
|
||||
myDir = Environment.DIRECTORY_MUSIC + "/" + context.getString(R.string.app_name);
|
||||
} else {
|
||||
myDir = Environment.DIRECTORY_DOWNLOADS;
|
||||
}
|
||||
|
||||
if (!new File(myDir).exists()) {
|
||||
boolean created = new File(myDir).mkdir();
|
||||
if (!created) {
|
||||
Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (mime.toLowerCase().startsWith("video")) {
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_MOVIES, context.getString(R.string.app_name) + "/" + fileName);
|
||||
} else if (mime.toLowerCase().startsWith("audio")) {
|
||||
|
|
|
@ -464,6 +464,11 @@ public class PinnedTimelineHelper {
|
|||
break;
|
||||
case NITTER:
|
||||
item.setIcon(R.drawable.nitter);
|
||||
if (pinnedTimeline.remoteInstance.displayName.trim().length() > 0) {
|
||||
item.setTitle(pinnedTimeline.remoteInstance.displayName);
|
||||
} else {
|
||||
item.setTitle(pinnedTimeline.remoteInstance.host);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -99,7 +99,9 @@ public class SpannableHelper {
|
|||
public static Spannable convert(Context context, String text,
|
||||
Status status, Account account, Announcement announcement,
|
||||
WeakReference<View> viewWeakReference, Status.Callback callback) {
|
||||
|
||||
if (text == null) {
|
||||
return null;
|
||||
}
|
||||
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
boolean customLight = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOMIZE_LIGHT_COLORS), false);
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package app.fedilab.android.ui.drawer;
|
||||
/* Copyright 2022 Thomas Schneider
|
||||
*
|
||||
* This file is a part of Fedilab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.appcompat.widget.LinearLayoutCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.cursoradapter.widget.SimpleCursorAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.android.R;
|
||||
import app.fedilab.android.activities.ProfileActivity;
|
||||
import app.fedilab.android.client.entities.api.Account;
|
||||
import app.fedilab.android.helper.Helper;
|
||||
|
||||
|
||||
public class AccountsSearchTopBarAdapter extends SimpleCursorAdapter {
|
||||
|
||||
private final int layout;
|
||||
private final LayoutInflater inflater;
|
||||
private final List<Account> accountList;
|
||||
|
||||
public AccountsSearchTopBarAdapter(Context context, List<Account> accounts, int layout, Cursor c, String[] from, int[] to, int flags) {
|
||||
super(context, layout, c, from, to, flags);
|
||||
this.layout = layout;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.accountList = accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return inflater.inflate(layout, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
super.bindView(view, context, cursor);
|
||||
|
||||
LinearLayoutCompat container = view.findViewById(R.id.account_container);
|
||||
container.setTag(cursor.getPosition());
|
||||
ImageView account_pp = view.findViewById(R.id.account_pp);
|
||||
container.setOnClickListener(v -> {
|
||||
int position = (int) v.getTag();
|
||||
if (accountList != null && accountList.size() > position) {
|
||||
Intent intent = new Intent(context, ProfileActivity.class);
|
||||
Bundle b = new Bundle();
|
||||
b.putSerializable(Helper.ARG_ACCOUNT, accountList.get(position));
|
||||
intent.putExtras(b);
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat
|
||||
.makeSceneTransitionAnimation((Activity) context, account_pp, context.getString(R.string.activity_porfile_pp));
|
||||
// start the new activity
|
||||
context.startActivity(intent, options.toBundle());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,23 +17,29 @@ package app.fedilab.android.ui.drawer;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.smarteist.autoimageslider.SliderViewAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.android.R;
|
||||
import app.fedilab.android.activities.MediaActivity;
|
||||
import app.fedilab.android.client.entities.api.Attachment;
|
||||
import app.fedilab.android.client.entities.api.Status;
|
||||
import app.fedilab.android.databinding.DrawerSliderBinding;
|
||||
import app.fedilab.android.helper.Helper;
|
||||
import jp.wasabeef.glide.transformations.BlurTransformation;
|
||||
|
||||
public class SliderAdapter extends SliderViewAdapter<SliderAdapter.SliderAdapterVH> {
|
||||
|
||||
|
@ -63,12 +69,38 @@ public class SliderAdapter extends SliderViewAdapter<SliderAdapter.SliderAdapter
|
|||
public void onBindViewHolder(SliderAdapterVH viewHolder, final int position) {
|
||||
|
||||
Attachment sliderItem = mSliderItems.get(position);
|
||||
|
||||
if (status.sensitive) {
|
||||
Glide.with(viewHolder.itemView)
|
||||
.load(sliderItem.preview_url)
|
||||
.centerCrop()
|
||||
.apply(new RequestOptions().transform(new BlurTransformation(50, 3)))
|
||||
.into(viewHolder.binding.ivAutoImageSlider);
|
||||
} else {
|
||||
Glide.with(viewHolder.itemView)
|
||||
.load(sliderItem.preview_url)
|
||||
.centerCrop()
|
||||
.into(viewHolder.binding.ivAutoImageSlider);
|
||||
}
|
||||
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final int timeout = sharedpreferences.getInt(context.getString(R.string.SET_NSFW_TIMEOUT), 5);
|
||||
boolean expand_media = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_MEDIA), false);
|
||||
|
||||
viewHolder.itemView.setOnClickListener(v -> {
|
||||
if (status.sensitive && !expand_media) {
|
||||
status.sensitive = false;
|
||||
notifyDataSetChanged();
|
||||
if (timeout > 0) {
|
||||
new CountDownTimer((timeout * 1000L), 1000) {
|
||||
public void onTick(long millisUntilFinished) {
|
||||
}
|
||||
|
||||
public void onFinish() {
|
||||
status.sensitive = true;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
} else {
|
||||
Intent mediaIntent = new Intent(context, MediaActivity.class);
|
||||
Bundle b = new Bundle();
|
||||
b.putInt(Helper.ARG_MEDIA_POSITION, position + 1);
|
||||
|
@ -78,6 +110,7 @@ public class SliderAdapter extends SliderViewAdapter<SliderAdapter.SliderAdapter
|
|||
.makeSceneTransitionAnimation((Activity) context, viewHolder.binding.ivAutoImageSlider, status.media_attachments.get(0).url);
|
||||
// start the new activity
|
||||
context.startActivity(mediaIntent, options.toBundle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -60,7 +60,6 @@ import android.widget.GridView;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -84,8 +83,8 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.ListPreloader;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.github.stom79.mytransl.MyTransL;
|
||||
import com.smarteist.autoimageslider.SliderAnimations;
|
||||
|
@ -137,7 +136,9 @@ import app.fedilab.android.databinding.LayoutMediaBinding;
|
|||
import app.fedilab.android.databinding.LayoutPollItemBinding;
|
||||
import app.fedilab.android.exception.DBException;
|
||||
import app.fedilab.android.helper.CrossActionHelper;
|
||||
import app.fedilab.android.helper.GlideApp;
|
||||
import app.fedilab.android.helper.GlideFocus;
|
||||
import app.fedilab.android.helper.GlideRequests;
|
||||
import app.fedilab.android.helper.Helper;
|
||||
import app.fedilab.android.helper.LongClickLinkMovementMethod;
|
||||
import app.fedilab.android.helper.MastodonHelper;
|
||||
|
@ -155,7 +156,7 @@ import es.dmoral.toasty.Toasty;
|
|||
import jp.wasabeef.glide.transformations.BlurTransformation;
|
||||
|
||||
|
||||
public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ListPreloader.PreloadModelProvider<Attachment> {
|
||||
public static final int STATUS_HIDDEN = 0;
|
||||
public static final int STATUS_VISIBLE = 1;
|
||||
public static final int STATUS_ART = 2;
|
||||
|
@ -172,6 +173,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
private boolean visiblePixelfed;
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private static float measuredWidth = -1;
|
||||
private static float measuredWidthArt = -1;
|
||||
|
||||
public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified, boolean canBeFederated, boolean checkRemotely) {
|
||||
this.statusList = statuses;
|
||||
|
@ -181,19 +184,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
this.checkRemotely = checkRemotely;
|
||||
}
|
||||
|
||||
public static int getStatusPosition(List<Status> timelineStatuses, Status status) {
|
||||
int position = 0;
|
||||
if (timelineStatuses != null && status != null) {
|
||||
for (Status _s : timelineStatuses) {
|
||||
if (_s.id.compareTo(status.id) == 0) {
|
||||
return position;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
private static boolean isVisiblePixelfed(Status status) {
|
||||
if (status.reblog != null) {
|
||||
|
@ -434,7 +424,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
psc.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||
holder.binding.statusContent.setLayoutParams(psc);
|
||||
LinearLayoutCompat.MarginLayoutParams pct = (LinearLayoutCompat.MarginLayoutParams) holder.binding.containerTrans.getLayoutParams();
|
||||
psc.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||
pct.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||
holder.binding.containerTrans.setLayoutParams(psc);
|
||||
LinearLayoutCompat.MarginLayoutParams pcv = (LinearLayoutCompat.MarginLayoutParams) holder.binding.card.getLayoutParams();
|
||||
pcv.setMarginStart((int) Helper.convertDpToPixel(6, context));
|
||||
|
@ -583,6 +573,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
gridView.setAdapter(new EmojiAdapter(emojis.get(BaseMainActivity.currentInstance)));
|
||||
gridView.setNumColumns(5);
|
||||
gridView.setOnItemClickListener((parent, view, index, id) -> {
|
||||
if (emojis.get(BaseMainActivity.currentInstance) == null) {
|
||||
return;
|
||||
}
|
||||
String emojiStr = emojis.get(BaseMainActivity.currentInstance).get(index).shortcode;
|
||||
String url = emojis.get(BaseMainActivity.currentInstance).get(index).url;
|
||||
String static_url = emojis.get(BaseMainActivity.currentInstance).get(index).static_url;
|
||||
|
@ -772,9 +765,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
CrossActionHelper.doCrossAction(context, CrossActionHelper.TypeOfCrossAction.BOOKMARK_ACTION, null, statusToDeal);
|
||||
return true;
|
||||
});
|
||||
holder.binding.actionButtonTranslate.setOnClickListener(v -> {
|
||||
translate(context, statusToDeal, holder, adapter);
|
||||
});
|
||||
holder.binding.actionButtonTranslate.setOnClickListener(v -> translate(context, statusToDeal, holder, adapter));
|
||||
holder.binding.actionButtonBookmark.setOnClickListener(v -> {
|
||||
if (remote) {
|
||||
Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show();
|
||||
|
@ -1283,7 +1274,18 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
holder.binding.mediaContainer.setVisibility(View.GONE);
|
||||
holder.binding.card.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (measuredWidth <= 0 && statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
|
||||
holder.binding.mediaContainer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
holder.binding.mediaContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
measuredWidth = holder.binding.mediaContainer.getWidth();
|
||||
if (adapter != null && statusList != null) {
|
||||
adapter.notifyItemChanged(0, statusList.size());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
|
||||
//--- MEDIA ATTACHMENT ---
|
||||
if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
|
||||
|
@ -1294,7 +1296,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
holder.binding.displayMedia.setVisibility(View.VISIBLE);
|
||||
holder.binding.displayMedia.setOnClickListener(v -> {
|
||||
statusToDeal.canLoadMedia = true;
|
||||
if (adapter != null) {
|
||||
adapter.notifyItemChanged(holder.getBindingAdapterPosition());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
int mediaPosition = 1;
|
||||
|
@ -1304,38 +1308,16 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
if (fullAttachement && (!statusToDeal.sensitive || expand_media)) {
|
||||
float ratio = 1.0f;
|
||||
float mediaH = -1.0f;
|
||||
float mediaW = -1.0f;
|
||||
if (attachment.meta != null && attachment.meta.small != null) {
|
||||
mediaH = attachment.meta.small.height;
|
||||
mediaW = attachment.meta.small.width;
|
||||
if (mediaW != 0) {
|
||||
ratio = measuredWidth > 0 ? measuredWidth / mediaW : 1.0f;
|
||||
}
|
||||
}
|
||||
loadAndAddAttachment(context, layoutMediaBinding, holder, adapter, mediaPosition, mediaW, mediaH, ratio, statusToDeal, attachment, singleMedia);
|
||||
|
||||
if (attachment.measuredWidth > 0) {
|
||||
float viewWidth = attachment.measuredWidth;
|
||||
if (attachment.meta != null && attachment.meta.small != null) {
|
||||
mediaH = attachment.meta.small.height;
|
||||
float mediaW = attachment.meta.small.width;
|
||||
if (mediaW != 0) {
|
||||
ratio = viewWidth / mediaW;
|
||||
}
|
||||
}
|
||||
loadAndAddAttachment(context, layoutMediaBinding, holder, adapter, mediaPosition, viewWidth, mediaH, ratio, statusToDeal, attachment, singleMedia);
|
||||
} else {
|
||||
int finalMediaPosition = mediaPosition;
|
||||
layoutMediaBinding.media.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
layoutMediaBinding.media.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
attachment.measuredWidth = layoutMediaBinding.media.getWidth();
|
||||
float ratio = 1.0f;
|
||||
float mediaH = -1.0f;
|
||||
float viewWidth = attachment.measuredWidth;
|
||||
if (attachment.meta != null && attachment.meta.small != null) {
|
||||
mediaH = attachment.meta.small.height;
|
||||
float mediaW = attachment.meta.small.width;
|
||||
if (mediaW != 0) {
|
||||
ratio = viewWidth / mediaW;
|
||||
}
|
||||
}
|
||||
loadAndAddAttachment(context, layoutMediaBinding, holder, adapter, finalMediaPosition, viewWidth, mediaH, ratio, statusToDeal, attachment, singleMedia);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
loadAndAddAttachment(context, layoutMediaBinding, holder, adapter, mediaPosition, -1.f, -1.f, -1.f, statusToDeal, attachment, singleMedia);
|
||||
}
|
||||
|
@ -1813,9 +1795,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
builderInner.setMessage(statusToDeal.account.acct);
|
||||
builderInner.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss());
|
||||
builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> accountsVM.muteHome(currentAccount, statusToDeal.account)
|
||||
.observe((LifecycleOwner) context, account -> {
|
||||
Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_LONG).show();
|
||||
}));
|
||||
.observe((LifecycleOwner) context, account -> Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_LONG).show()));
|
||||
builderInner.show();
|
||||
} else if (itemId == R.id.action_mute_conversation) {
|
||||
if (statusToDeal.muted) {
|
||||
|
@ -2113,10 +2093,42 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
}
|
||||
}
|
||||
|
||||
private static RequestBuilder<Drawable> prepareRequestBuilder(Context context, Attachment attachment,
|
||||
float mediaW, float mediaH,
|
||||
float focusX, float focusY, boolean isSensitive, boolean isArt) {
|
||||
|
||||
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean fullAttachement = sharedpreferences.getBoolean(context.getString(R.string.SET_FULL_PREVIEW), false);
|
||||
if (isArt) {
|
||||
fullAttachement = true;
|
||||
}
|
||||
boolean expand_media = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_MEDIA), false);
|
||||
RequestBuilder<Drawable> requestBuilder;
|
||||
GlideRequests glideRequests = GlideApp.with(context);
|
||||
if (!isSensitive || expand_media) {
|
||||
requestBuilder = glideRequests.asDrawable();
|
||||
if (!fullAttachement) {
|
||||
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)));
|
||||
requestBuilder = requestBuilder.dontAnimate();
|
||||
} else {
|
||||
requestBuilder = requestBuilder.placeholder(R.color.transparent_grey);
|
||||
requestBuilder = requestBuilder.dontAnimate();
|
||||
requestBuilder = requestBuilder.apply(new RequestOptions().override((int) mediaW, (int) mediaH));
|
||||
requestBuilder = requestBuilder.fitCenter();
|
||||
}
|
||||
} else {
|
||||
requestBuilder = glideRequests.asDrawable()
|
||||
.dontAnimate()
|
||||
.apply(new RequestOptions().transform(new BlurTransformation(50, 3)));
|
||||
// .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
|
||||
}
|
||||
return requestBuilder;
|
||||
}
|
||||
|
||||
private static void loadAndAddAttachment(Context context, LayoutMediaBinding layoutMediaBinding,
|
||||
StatusViewHolder holder,
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder> adapter,
|
||||
int mediaPosition, float viewWidth, float mediaH, float ratio,
|
||||
int mediaPosition, float mediaW, float mediaH, float ratio,
|
||||
Status statusToDeal, Attachment attachment, boolean singleImage) {
|
||||
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final int timeout = sharedpreferences.getInt(context.getString(R.string.SET_NSFW_TIMEOUT), 5);
|
||||
|
@ -2179,26 +2191,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
layoutMediaBinding.viewDescription.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
RequestBuilder<Drawable> requestBuilder = prepareRequestBuilder(context, attachment, mediaW * ratio, mediaH * ratio, focusX, focusY, statusToDeal.sensitive, false);
|
||||
if (!statusToDeal.sensitive || expand_media) {
|
||||
layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_24);
|
||||
RequestBuilder<Drawable> requestBuilder = Glide.with(layoutMediaBinding.media.getContext())
|
||||
.load(attachment.preview_url);
|
||||
if (!fullAttachement) {
|
||||
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new GlideFocus(focusX, focusY)));
|
||||
} else {
|
||||
requestBuilder = requestBuilder.placeholder(R.color.transparent_grey);
|
||||
requestBuilder = requestBuilder.apply(new RequestOptions().override((int) viewWidth, (int) mediaH));
|
||||
requestBuilder = requestBuilder.fitCenter();
|
||||
}
|
||||
requestBuilder.into(layoutMediaBinding.media);
|
||||
} else {
|
||||
layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24);
|
||||
Glide.with(layoutMediaBinding.media.getContext())
|
||||
.load(attachment.preview_url)
|
||||
.apply(new RequestOptions().transform(new BlurTransformation(50, 3)))
|
||||
// .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
|
||||
.into(layoutMediaBinding.media);
|
||||
}
|
||||
requestBuilder.load(attachment.preview_url).into(layoutMediaBinding.media);
|
||||
if (statusToDeal.sensitive) {
|
||||
Helper.changeDrawableColor(context, layoutMediaBinding.viewHide, ThemeHelper.getAttColor(context, R.attr.colorError));
|
||||
} else {
|
||||
|
@ -2247,6 +2246,53 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Attachment> getPreloadItems(int position) {
|
||||
List<Attachment> attachments = new ArrayList<>();
|
||||
if (position == 0 && statusList.size() > 0) {
|
||||
for (Status status : statusList.subList(0, 1)) {
|
||||
Status statusToDeal = status.reblog != null ? status.reblog : status;
|
||||
if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
|
||||
attachments.addAll(statusToDeal.media_attachments);
|
||||
}
|
||||
}
|
||||
} else if (position > 0 && position < (statusList.size() - 1)) {
|
||||
for (Status status : statusList.subList(position - 1, position + 1)) {
|
||||
Status statusToDeal = status.reblog != null ? status.reblog : status;
|
||||
if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
|
||||
attachments.addAll(statusToDeal.media_attachments);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Status status : statusList.subList(position, position)) {
|
||||
Status statusToDeal = status.reblog != null ? status.reblog : status;
|
||||
if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
|
||||
attachments.addAll(statusToDeal.media_attachments);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RequestBuilder<Drawable> getPreloadRequestBuilder(@NonNull Attachment attachment) {
|
||||
float focusX = 0.f;
|
||||
float focusY = 0.f;
|
||||
if (attachment.meta != null && attachment.meta.focus != null) {
|
||||
focusX = attachment.meta.focus.x;
|
||||
focusY = attachment.meta.focus.y;
|
||||
}
|
||||
int mediaH = 0;
|
||||
int mediaW = 0;
|
||||
if (attachment.meta != null && attachment.meta.small != null) {
|
||||
mediaH = attachment.meta.small.height;
|
||||
mediaW = attachment.meta.small.width;
|
||||
}
|
||||
return prepareRequestBuilder(context, attachment, mediaW, mediaH, focusX, focusY, attachment.sensitive, timelineType == Timeline.TimeLineEnum.ART).load(attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a broadcast to other open fragments that content a timeline
|
||||
*
|
||||
|
@ -2549,45 +2595,29 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
} else if (viewHolder.getItemViewType() == STATUS_ART) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
MastodonHelper.loadPPMastodon(holder.bindingArt.artPp, status.account);
|
||||
if (status.art_attachment != null) {
|
||||
|
||||
if (measuredWidthArt <= 0) {
|
||||
holder.bindingArt.artMedia.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
holder.bindingArt.artMedia.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
if (status.art_attachment.meta != null && status.art_attachment.meta.small != null) {
|
||||
float viewWidth = holder.bindingArt.artMedia.getWidth();
|
||||
ConstraintLayout.LayoutParams lp;
|
||||
float mediaH = status.art_attachment.meta.small.height;
|
||||
float mediaW = status.art_attachment.meta.small.width;
|
||||
float ratio = 1.0f;
|
||||
if (mediaW != 0) {
|
||||
ratio = viewWidth / mediaW;
|
||||
}
|
||||
lp = new ConstraintLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, (int) (mediaH * ratio));
|
||||
holder.bindingArt.artMedia.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
holder.bindingArt.artMedia.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
measuredWidthArt = holder.bindingArt.artMedia.getWidth();
|
||||
notifyItemChanged(0, statusList.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
if (status.art_attachment != null) {
|
||||
if (status.art_attachment.meta != null && status.art_attachment.meta.small != null) {
|
||||
float viewWidth = holder.bindingArt.artMedia.getWidth();
|
||||
ConstraintLayout.LayoutParams lp;
|
||||
float mediaH = status.art_attachment.meta.small.height;
|
||||
float mediaW = status.art_attachment.meta.small.width;
|
||||
float ratio = 1.0f;
|
||||
if (mediaW != 0) {
|
||||
ratio = viewWidth / mediaW;
|
||||
}
|
||||
float ratio = measuredWidthArt > 0 ? measuredWidthArt / mediaW : 1.0f;
|
||||
lp = new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, (int) (mediaH * ratio));
|
||||
holder.bindingArt.artMedia.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
holder.bindingArt.artMedia.setLayoutParams(lp);
|
||||
RequestBuilder<Drawable> requestBuilder = prepareRequestBuilder(context, status.art_attachment, mediaW * ratio, mediaH * ratio, 1.0f, 1.0f, status.sensitive, true);
|
||||
requestBuilder.into(holder.bindingArt.artMedia);
|
||||
}
|
||||
Glide.with(holder.bindingArt.artMedia.getContext())
|
||||
.load(status.art_attachment.preview_url)
|
||||
.apply(new RequestOptions().transform(new RoundedCorners((int) Helper.convertDpToPixel(3, context))))
|
||||
.into(holder.bindingArt.artMedia);
|
||||
|
||||
}
|
||||
holder.bindingArt.artUsername.setText(
|
||||
status.account.getSpanDisplayName(context,
|
||||
|
@ -2671,6 +2701,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
super.onViewRecycled(holder);
|
||||
}
|
||||
|
||||
|
||||
public interface FetchMoreCallBack {
|
||||
void onClickMinId(String min_id, Status statusToUpdate);
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package app.fedilab.android.ui.drawer;
|
||||
/* Copyright 2022 Thomas Schneider
|
||||
*
|
||||
* This file is a part of Fedilab
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.appcompat.widget.LinearLayoutCompat;
|
||||
import androidx.cursoradapter.widget.SimpleCursorAdapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import app.fedilab.android.R;
|
||||
import app.fedilab.android.activities.HashTagActivity;
|
||||
import app.fedilab.android.client.entities.api.Tag;
|
||||
import app.fedilab.android.helper.Helper;
|
||||
|
||||
|
||||
public class TagSearchTopBarAdapter extends SimpleCursorAdapter {
|
||||
|
||||
private final int layout;
|
||||
private final LayoutInflater inflater;
|
||||
private final List<Tag> tags;
|
||||
|
||||
public TagSearchTopBarAdapter(Context context, List<Tag> tags, int layout, Cursor c, String[] from, int[] to, int flags) {
|
||||
super(context, layout, c, from, to, flags);
|
||||
this.layout = layout;
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return inflater.inflate(layout, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
super.bindView(view, context, cursor);
|
||||
|
||||
LinearLayoutCompat container = view.findViewById(R.id.tag_container);
|
||||
container.setTag(cursor.getPosition());
|
||||
container.setOnClickListener(v -> {
|
||||
int position = (int) v.getTag();
|
||||
if (tags != null && tags.size() > position) {
|
||||
Intent intent = new Intent(context, HashTagActivity.class);
|
||||
Bundle b = new Bundle();
|
||||
b.putString(Helper.ARG_SEARCH_KEYWORD, tags.get(position).name.trim());
|
||||
intent.putExtras(b);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -74,7 +74,7 @@ public class FragmentMediaProfile extends Fragment {
|
|||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
flagLoading = false;
|
||||
accountsVM = new ViewModelProvider(FragmentMediaProfile.this).get(AccountsVM.class);
|
||||
accountsVM = new ViewModelProvider(requireActivity()).get(AccountsVM.class);
|
||||
mediaStatuses = new ArrayList<>();
|
||||
|
||||
if (checkRemotely) {
|
||||
|
|
|
@ -33,6 +33,7 @@ import java.util.List;
|
|||
|
||||
import app.fedilab.android.BaseMainActivity;
|
||||
import app.fedilab.android.R;
|
||||
import app.fedilab.android.activities.SearchResultTabActivity;
|
||||
import app.fedilab.android.client.entities.api.Tag;
|
||||
import app.fedilab.android.client.entities.app.Timeline;
|
||||
import app.fedilab.android.databinding.FragmentPaginationBinding;
|
||||
|
@ -144,6 +145,9 @@ public class FragmentMastodonTag extends Fragment {
|
|||
router();
|
||||
});
|
||||
if (tags == null || tags.size() == 0) {
|
||||
if (requireActivity() instanceof SearchResultTabActivity) {
|
||||
((SearchResultTabActivity) requireActivity()).moveToAccount();
|
||||
}
|
||||
binding.recyclerView.setVisibility(View.GONE);
|
||||
binding.noAction.setVisibility(View.VISIBLE);
|
||||
binding.noActionText.setText(R.string.no_tags);
|
||||
|
|
|
@ -40,6 +40,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader;
|
||||
import com.bumptech.glide.util.ViewPreloadSizeProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -60,6 +63,7 @@ import app.fedilab.android.client.entities.app.Timeline;
|
|||
import app.fedilab.android.databinding.FragmentPaginationBinding;
|
||||
import app.fedilab.android.exception.DBException;
|
||||
import app.fedilab.android.helper.CrossActionHelper;
|
||||
import app.fedilab.android.helper.GlideApp;
|
||||
import app.fedilab.android.helper.Helper;
|
||||
import app.fedilab.android.helper.MastodonHelper;
|
||||
import app.fedilab.android.ui.drawer.StatusAdapter;
|
||||
|
@ -84,6 +88,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
private StatusAdapter statusAdapter;
|
||||
private Timeline.TimeLineEnum timelineType;
|
||||
private List<Status> timelineStatuses;
|
||||
private static final int PRELOAD_AHEAD_ITEMS = 10;
|
||||
private ViewPreloadSizeProvider<Attachment> preloadSizeProvider;
|
||||
//Handle actions that can be done in other fragments
|
||||
private final BroadcastReceiver receive_action = new BroadcastReceiver() {
|
||||
@Override
|
||||
|
@ -161,10 +167,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
if (toRemove.size() > 0) {
|
||||
for (int i = 0; i < toRemove.size(); i++) {
|
||||
int position = getPosition(toRemove.get(i));
|
||||
if (position >= 0) {
|
||||
timelineStatuses.remove(position);
|
||||
statusAdapter.notifyItemRemoved(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (refreshAll) {
|
||||
refreshAllAdapters();
|
||||
}
|
||||
|
@ -266,6 +274,29 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
return found ? position : -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the position of the status in the ArrayList
|
||||
*
|
||||
* @param status - Status to fetch
|
||||
* @return position or -1 if not found
|
||||
*/
|
||||
private int getAbsolutePosition(Status status) {
|
||||
int position = 0;
|
||||
boolean found = false;
|
||||
if (status.id == null) {
|
||||
return -1;
|
||||
}
|
||||
for (Status _status : timelineStatuses) {
|
||||
if (_status.id != null && _status.id.compareTo(status.id) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
return found ? position : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned list of checked status id for reports
|
||||
*
|
||||
|
@ -395,6 +426,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
if (timelineType != null) {
|
||||
slug = timelineType != Timeline.TimeLineEnum.ART ? timelineType.getValue() + (ident != null ? "|" + ident : "") : Timeline.TimeLineEnum.TAG.getValue() + (ident != null ? "|" + ident : "");
|
||||
}
|
||||
|
||||
|
||||
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION));
|
||||
binding = FragmentPaginationBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
|
@ -412,7 +445,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
binding.swipeContainer.setRefreshing(false);
|
||||
binding.loadingNextElements.setVisibility(View.GONE);
|
||||
flagLoading = false;
|
||||
int currentPosition = mLayoutManager.findFirstVisibleItemPosition();
|
||||
if (timelineStatuses != null && fetched_statuses != null && fetched_statuses.statuses != null && fetched_statuses.statuses.size() > 0) {
|
||||
try {
|
||||
if (statusToUpdate != null) {
|
||||
|
@ -475,9 +507,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
update.onUpdate(0, timelineType, slug);
|
||||
}
|
||||
if (direction == DIRECTION.TOP && fetchingMissing) {
|
||||
int newPosition = currentPosition + fetched_statuses.statuses.size() + 1;
|
||||
if (newPosition < timelineStatuses.size()) {
|
||||
binding.recyclerView.scrollToPosition(newPosition);
|
||||
int position = getAbsolutePosition(fetched_statuses.statuses.get(fetched_statuses.statuses.size() - 1));
|
||||
|
||||
if (position != -1) {
|
||||
binding.recyclerView.scrollToPosition(position + 1);
|
||||
}
|
||||
}
|
||||
if (!fetchingMissing) {
|
||||
|
@ -611,6 +644,14 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
|
|||
mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
binding.recyclerView.setLayoutManager(mLayoutManager);
|
||||
binding.recyclerView.setAdapter(statusAdapter);
|
||||
|
||||
preloadSizeProvider = new ViewPreloadSizeProvider<>();
|
||||
RecyclerViewPreloader<Attachment> preloader =
|
||||
new RecyclerViewPreloader<>(
|
||||
GlideApp.with(this), statusAdapter, preloadSizeProvider, PRELOAD_AHEAD_ITEMS);
|
||||
binding.recyclerView.addOnScrollListener(preloader);
|
||||
binding.recyclerView.setItemViewCacheSize(0);
|
||||
|
||||
if (timelineType != Timeline.TimeLineEnum.TREND_MESSAGE) {
|
||||
binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
-->
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/tag_container"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
|
|
@ -971,4 +971,6 @@
|
|||
<string name="login_status">Stav přihlášení</string>
|
||||
<string name="joined">Připojil(a) se</string>
|
||||
<string name="silenced">Ztišen(a)</string>
|
||||
<string name="set_display_compact_buttons">Kompaktní tlačítka akcí</string>
|
||||
<string name="set_display_compact_buttons_description">Tlačítka v dolní části zpráv nezaberou celou šířku</string>
|
||||
</resources>
|
|
@ -398,7 +398,7 @@
|
|||
<string name="settings_category_notif_categories">Kategorien</string>
|
||||
<string name="move_timeline">Timeline verschieben</string>
|
||||
<string name="hide_timeline">Timeline ausblenden</string>
|
||||
<string name="reorder_timelines">Verwaltung der Timelines</string>
|
||||
<string name="reorder_timelines">Timelines verwalten</string>
|
||||
<string name="reorder_list_deleted">Liste endgültig gelöscht</string>
|
||||
<string name="reorder_instance_removed">Gefolgte Instanz entfernt</string>
|
||||
<string name="reorder_tag_removed">Angehefteter Hashtag entfernt</string>
|
||||
|
@ -639,7 +639,7 @@
|
|||
<string name="notif_display_poll_results">Ergebnisse der Umfrage</string>
|
||||
<string name="mark_all_as_read">Alle Benachrichtigungen als gelesen markieren</string>
|
||||
<string name="clear_all_notif">Alle Benachrichtigungen entfernen</string>
|
||||
<string name="scheduled">Geplant</string>
|
||||
<string name="scheduled">Beiträge planen</string>
|
||||
<string name="profiled_updated">Profil wurde aktualisiert!</string>
|
||||
<string name="not_valid_list_name">Listenname ist nicht gültig!</string>
|
||||
<string name="no_account_in_list">Keine Konten für diese Liste gefunden!</string>
|
||||
|
@ -964,4 +964,7 @@
|
|||
<string name="set_remote_profile_title">Profile auf anderen Instanzen</string>
|
||||
<string name="local_only">Nur Lokal</string>
|
||||
<string name="set_display_local_only">Zeige den Knopf \"Nur Lokal\"</string>
|
||||
<string name="set_pixelfed_presentation">Pixelfed-Präsentation für Medien</string>
|
||||
<string name="set_display_compact_buttons_description">Knöpfe am unteren Rand von Beiträgen benötigen nicht die gesamte Breite</string>
|
||||
<string name="set_display_compact_buttons">Kompakte Aktions-Knöpfe</string>
|
||||
</resources>
|
|
@ -938,4 +938,21 @@
|
|||
<string name="admin_domainblock_public_comment">Commentaire à propos de la limitation de ce domaine au public, si l\'annonce de la liste de limitation de domaines est activée.</string>
|
||||
<string name="group_reblogs">Re-blog de groupe dans la timeline d’accueil</string>
|
||||
<string name="add_all_users_home_muted">Ajouter tout les utilisateurs masqués à l’accueil</string>
|
||||
<string name="api_key">Clé API du traducteur</string>
|
||||
<string name="icons_extra_features_visibility_summary">Si votre instance n\'accepte pas certaines fonctionnalités supplémentaires, vous pouvez masquer ces icônes</string>
|
||||
<string name="set_extand_extra_features">En activant cette option, l\'application affichera des fonctionnalités supplémentaires. Cette fonctionnalité est utilisée pour les logiciels sociaux comme Pleroma, Akkoma ou Glitch Social</string>
|
||||
<string name="set_translator_version">Version du traducteur</string>
|
||||
<string name="set_extand_extra_features_title">Fonctionnalités supplémentaires</string>
|
||||
<string name="icons_visibility_summary">Vous pouvez sans risque cacher ces icônes en bas pour avoir plus d\'espace. Elles se trouvent également dans le sous-menu.</string>
|
||||
<string name="icons_extra_features">Icônes pour les fonctions supplémentaires</string>
|
||||
<string name="bubble">Bulle</string>
|
||||
<string name="reply_visibility">Visibilité des réponses</string>
|
||||
<string name="v_list">Liste</string>
|
||||
<string name="following">Suivant</string>
|
||||
<string name="icons_visibility">Visibilité des icônes</string>
|
||||
<string name="translator">Traducteur</string>
|
||||
<string name="set_translator">Traducteur</string>
|
||||
<string name="set_remove_left_margin_title">Supprimer la marge de gauche</string>
|
||||
<string name="set_remove_left_margin">Suppression de la marge de gauche dans les lignes de temps pour rendre les messages plus compacts</string>
|
||||
<string name="version">Version</string>
|
||||
</resources>
|
|
@ -958,4 +958,7 @@
|
|||
<string name="set_translator_version">Versión do tradutor</string>
|
||||
<string name="icons_visibility_summary">Podes agochar tranquilamente estas iconas ao pé para ter máis espazo. Están tamén no submenú.</string>
|
||||
<string name="set_remote_profile">A app mostrará públicamente os perfís para obter tódalas mensaxes. As interaccións precisarán un paso extra para federar as mensaxes.</string>
|
||||
<string name="set_pixelfed_presentation">Presentación Pixelfed para multimedia</string>
|
||||
<string name="set_display_compact_buttons">Compactar botóns de accións</string>
|
||||
<string name="set_display_compact_buttons_description">Os botóns ao pé das mensaxes non ocuparán todo o ancho</string>
|
||||
</resources>
|
|
@ -3,7 +3,7 @@
|
|||
<string name="action_about">Over</string>
|
||||
<string name="action_about_instance">Over deze server</string>
|
||||
<string name="action_privacy">Privacy</string>
|
||||
<string name="action_cache">Buffer</string>
|
||||
<string name="action_cache">Cache</string>
|
||||
<string name="action_logout">Uitloggen</string>
|
||||
<!-- common -->
|
||||
<string name="close">Sluiten</string>
|
||||
|
@ -548,7 +548,7 @@
|
|||
<string name="status_count">Status telling</string>
|
||||
<string name="instance_count">Server-teller</string>
|
||||
<string name="poll_finish_in">Eindigt over %s</string>
|
||||
<string name="no_instance_reccord">Deze server is niet beschikbaar</string>
|
||||
<string name="no_instance_reccord">Deze server is niet beschikbaar op https://instances.social</string>
|
||||
<string name="display_full_link">Add a comment</string>
|
||||
<string name="share_link">Deel link</string>
|
||||
<string name="open_other_app">Open met een andere app</string>
|
||||
|
@ -627,7 +627,7 @@
|
|||
<string name="set_use_cache_indication">Tijdlijnen worden in cache gezet zodat de app sneller is.</string>
|
||||
<string name="load_media_type_title">Laad thumbnails voor media</string>
|
||||
<string name="display_timelines">Toon tijdlijnen</string>
|
||||
<string name="cached_messages">Gebufferd bericht</string>
|
||||
<string name="cached_messages">Bericht in cache</string>
|
||||
<string name="display_options">Toon opties</string>
|
||||
<string name="action_unpin">Bericht losmaken</string>
|
||||
<string name="edited_message_at">Bewerkt op %1$s</string>
|
||||
|
@ -683,7 +683,7 @@
|
|||
<string name="messages_in_cache_for_other_timelines">Berichten in cache andere tijdlijnen</string>
|
||||
<string name="bottom_menu">Onderste menu</string>
|
||||
<string name="tap_here_to_refresh_poll">Klik hier om poll bij te werken</string>
|
||||
<string name="clear_cache">Buffer leegmaken</string>
|
||||
<string name="clear_cache">Cache leegmaken</string>
|
||||
<string name="msg_save_image">Wil je afsluiten zonder de afbeelding te bewaren\?</string>
|
||||
<string name="default_system_language">Gebruik de standaard systeemtaal</string>
|
||||
<string name="set_your_max_char_count">Stel je maximale karakter limiet in</string>
|
||||
|
@ -953,4 +953,19 @@
|
|||
<string name="set_display_reaction_indication">Toon \"Reacties\" knop</string>
|
||||
<string name="group_reblogs">Groepeer reblogs in eigen tijdlijn</string>
|
||||
<string name="set_remove_left_margin">Verwijder linker kantlijn in tijdlijnen voor compactere berichten</string>
|
||||
<string name="following">Volgend</string>
|
||||
<string name="self">Zelf</string>
|
||||
<string name="set_remote_profile_title">Remote profielen</string>
|
||||
<string name="reply_visibility">Zichtbaarheid van antwoorden</string>
|
||||
<string name="local_only">Alleen lokaal</string>
|
||||
<string name="set_display_compact_buttons">Compacte actieknoppen</string>
|
||||
<string name="set_display_compact_buttons_description">Knoppen aan de onderkant van berichten nemen niet de hele breedte in</string>
|
||||
<string name="set_pixelfed_presentation">Pixelfed presentatie voor media</string>
|
||||
<string name="set_remote_profile">De app toont openbare profielen om alle berichten te ontvangen. Interacties hebben een extra stap nodig om berichten te bundelen.</string>
|
||||
<string name="bubble">Bubbel</string>
|
||||
<string name="exclude_visibility">Zichtbaarheid uitsluiten</string>
|
||||
<string name="v_list">Lijst</string>
|
||||
<string name="set_display_local_only">Toon \"Alleen lokaal\" knop</string>
|
||||
<string name="set_post_format">Bericht indeling</string>
|
||||
<string name="post_format">Bericht indeling</string>
|
||||
</resources>
|
|
@ -316,7 +316,9 @@
|
|||
<string name="channel_notif_media">Загрузка медиа</string>
|
||||
<string name="select_sound">Выбрать сигнал</string>
|
||||
<string name="set_enable_time_slot">Активировать временной интервал</string>
|
||||
<string name="block_domain_confirm_message">Вы уверены, что хотите заблокировать %s\?</string>
|
||||
<string name="block_domain_confirm_message">Вы уверены, что хотите заблокировать %s\?
|
||||
\n
|
||||
\nВы не будете получать уведомлений и видеть публикации из этого домена. Ваши подписчики из этого домена будут удалены.</string>
|
||||
<string name="block_domain">Заблокировать домен</string>
|
||||
<string name="toast_block_domain">Этот домен заблокирован</string>
|
||||
<string name="retrieve_remote_status">Получение внешнего статуса</string>
|
||||
|
@ -716,4 +718,26 @@
|
|||
<string name="data_import_settings_success">Настройки успешно импортированы</string>
|
||||
<string name="data_export_settings">Настройки экспортированы</string>
|
||||
<string name="boosted_by">Продвинут</string>
|
||||
<string name="report_1_block">Вы не будете видеть публикации этого ползователя. Он не сможет видеть ваши публикации или подписаться на вас. Пользователь сможет понять, что был заблокирован.</string>
|
||||
<string name="report_3_title">Какие правила были нарушены\?</string>
|
||||
<string name="report_all_more">Выберите подходящие пункты</string>
|
||||
<string name="report_more_remote">Этот аккаунт принадлежит другому серверу. Отправить анонимную копию жалобы туда\?</string>
|
||||
<string name="set_display_compact_buttons_description">Кнопки внизу сообщений не будут занимать всю ширину</string>
|
||||
<string name="instance_health_checkedat">Проверено в: %s</string>
|
||||
<string name="post_message_text">Отправка сообщения %d/%d</string>
|
||||
<string name="is_down">Не работает!</string>
|
||||
<string name="report_1_title">Не хотите это видеть\?</string>
|
||||
<string name="report_1_unfollow">Вы подписаны на этот аккаунт. Чтобы перестать видеть его публикации, отпишитесь от него.</string>
|
||||
<string name="report_1_block_title">Заблокировать %1$s</string>
|
||||
<string name="channel_notif_signup">Новая регистрация</string>
|
||||
<string name="notif_update_push">Сообщение, которым вы поделились, было изменено</string>
|
||||
<string name="instance_health_uptime">Онлайн: %,.2f %%</string>
|
||||
<string name="report_2_title">Есть ли какие-либо публикации, которые подтверждают жалобу\?</string>
|
||||
<string name="report_sent">Жалоба отправлена!</string>
|
||||
<string name="add_status">Добавить статус</string>
|
||||
<string name="remove_status">Убрать статус</string>
|
||||
<string name="post_message">Сообщение публикуется…</string>
|
||||
<string name="is_up">Работает!</string>
|
||||
<string name="report_more_additional">Дополнительные комментарии</string>
|
||||
<string name="report_more">Есть ли что-то ещё, что нам надо знать\?</string>
|
||||
</resources>
|
|
@ -771,7 +771,7 @@
|
|||
<string name="action_unpin">Isbloca su messàgiu</string>
|
||||
<string name="toast_unpin">Su messàgiu no est prus apicadu!</string>
|
||||
<string name="toast_pin">Su messàgiu est istadu apicadu</string>
|
||||
<string name="set_live_translate_title">Borta sos messàgios</string>
|
||||
<string name="set_live_translate_title">Bortare sos messàgios</string>
|
||||
<string name="set_live_translate">Fortza sa tradutzione a una limba ispetzificada. Issèbera su primu valore pro torrare a is cunfiguratziones de su dispositivu</string>
|
||||
<string name="edit_message">Modìfica su messàgiu</string>
|
||||
<string name="full_date_edited">%1$s at modificadu %2$s</string>
|
||||
|
@ -950,4 +950,11 @@
|
|||
<string name="set_extand_extra_features">Ativende cussa optzione s\'aplicatzione at a ammustrare funtzionalidades extra. Custa funtzionalidade b\'est pro programmas sotziales che a Pleroma, Akkoma o Glitch Social</string>
|
||||
<string name="icons_visibility_summary">Podes cuare custas iconas in manera segura in fundu pro tènnere prus logu. Sunt fintzas in su suta-menù.</string>
|
||||
<string name="icons_extra_features_visibility_summary">Si s\'istàntzia tua no atzetat unas cantas funtzionalidades extra podes cuare custas iconas</string>
|
||||
<string name="set_remote_profile">S\'aplicatzione at a ammustrare profilos in manera pùblica pro retzire totu is messàgios. Is interatziones ant a bisongiare de unu passu in prus pro federare is messàgios.</string>
|
||||
<string name="set_pixelfed_presentation">Presentatzione de Pixelfed pro is mèdios</string>
|
||||
<string name="set_display_compact_buttons">Butones de atziones cumpatos</string>
|
||||
<string name="set_display_compact_buttons_description">Is butones in fundu a is messàgios no ant a pigare totu sa largària</string>
|
||||
<string name="set_display_local_only">Mustra su butone \"In locale ebbia\"</string>
|
||||
<string name="local_only">In locale ebbia</string>
|
||||
<string name="reply_visibility">Visibilidade de is rispostas</string>
|
||||
</resources>
|
|
@ -962,4 +962,7 @@
|
|||
<string name="set_remote_profile">Uygulama, tüm mesajları almak için herkese açık profilleri görüntüleyecektir. Etkileşimlerin mesajları birleştirmek için ek bir adıma ihtiyacı olacaktır.</string>
|
||||
<string name="set_display_local_only">\"Yalnızca yerel\" düğmesini göster</string>
|
||||
<string name="local_only">Yalnızca yerel</string>
|
||||
<string name="set_pixelfed_presentation">Medya için Pixelfed sunumu</string>
|
||||
<string name="set_display_compact_buttons">Küçük eylem düğmeleri</string>
|
||||
<string name="set_display_compact_buttons_description">Mesajların altındaki düğmeler tüm genişliği kaplamayacak</string>
|
||||
</resources>
|
13
src/fdroid/fastlane/metadata/android/en/changelogs/463.txt
Normal file
13
src/fdroid/fastlane/metadata/android/en/changelogs/463.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
Added:
|
||||
- Search bar: display suggestions when starting by "@" or "#"
|
||||
|
||||
Changed:
|
||||
- Preload media in timelines to avoid jumps
|
||||
- Search: Automatically switch to account tab if no results for tags
|
||||
|
||||
Fixed:
|
||||
- Fix jumps with the fetch more feature
|
||||
- Fix videos cannot be saved
|
||||
- Tags cannot be pinned when there are no custom tabs
|
||||
- PixelFed view: NSFW not honored
|
||||
- Fix crashes
|
1
src/fdroid/fastlane/metadata/android/nl/title.txt
Normal file
1
src/fdroid/fastlane/metadata/android/nl/title.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Fedilab
|
Loading…
Reference in a new issue