diff --git a/app/build.gradle b/app/build.gradle
index e39b62ae..eaabe38d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,8 +13,8 @@ android {
defaultConfig {
minSdk 21
targetSdk 33
- versionCode 463
- versionName "3.14.1"
+ versionCode 464
+ versionName "3.14.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
flavorDimensions "default"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6543017c..61a0d0a4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -249,6 +249,11 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/Suggestions"
android:theme="@style/AppThemeBar" />
+
. */
+
+import static app.fedilab.android.client.entities.app.Timeline.TimeLineEnum.ACCOUNT_DIRECTORY;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import org.jetbrains.annotations.NotNull;
+
+import app.fedilab.android.R;
+import app.fedilab.android.databinding.ActivityDirectoryBinding;
+import app.fedilab.android.helper.Helper;
+import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount;
+
+
+public class DirectoryActivity extends BaseBarActivity {
+
+ private static boolean local = false;
+ private static String order = "active";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ActivityDirectoryBinding binding = ActivityDirectoryBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ if (getSupportActionBar() != null) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(Helper.ARG_DIRECTORY_LOCAL, local);
+ bundle.putString(Helper.ARG_DIRECTORY_ORDER, order);
+ bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, ACCOUNT_DIRECTORY);
+ Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_directory, new FragmentMastodonAccount(), bundle, null, null);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_directory, menu);
+ if (order.equals("active")) {
+ menu.findItem(R.id.order_active).setChecked(true);
+ } else {
+ menu.findItem(R.id.order_new).setChecked(true);
+ }
+ menu.findItem(R.id.action_local).setChecked(local);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(@NotNull MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ } else if (item.getItemId() == R.id.action_local) {
+ item.setChecked(!item.isChecked());
+ local = item.isChecked();
+ } else if (item.getItemId() == R.id.order_active) {
+ order = "active";
+ } else if (item.getItemId() == R.id.order_new) {
+ order = "new";
+ }
+ recreate();
+ return super.onOptionsItemSelected(item);
+ }
+
+}
diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java
index 1ab57d6d..27e59b80 100644
--- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java
+++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java
@@ -34,6 +34,7 @@ import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -76,6 +77,7 @@ import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Attachment;
+import app.fedilab.android.client.entities.api.FamiliarFollowers;
import app.fedilab.android.client.entities.api.Field;
import app.fedilab.android.client.entities.api.IdentityProof;
import app.fedilab.android.client.entities.api.MastodonList;
@@ -86,6 +88,7 @@ import app.fedilab.android.client.entities.app.RemoteInstance;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.client.entities.app.WellKnownNodeinfo;
import app.fedilab.android.databinding.ActivityProfileBinding;
+import app.fedilab.android.databinding.NotificationsRelatedAccountsBinding;
import app.fedilab.android.exception.DBException;
import app.fedilab.android.helper.CrossActionHelper;
import app.fedilab.android.helper.Helper;
@@ -106,6 +109,7 @@ public class ProfileActivity extends BaseActivity {
private RelationShip relationship;
+ private FamiliarFollowers familiarFollowers;
private Account account;
private ScheduledExecutorService scheduledExecutorService;
private action doAction;
@@ -255,6 +259,13 @@ public class ProfileActivity extends BaseActivity {
updateAccount();
}
});
+ accountsVM.getFamiliarFollowers(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, accountListToCheck).observe(ProfileActivity.this, familiarFollowersList -> {
+ if (familiarFollowersList != null && familiarFollowersList.size() > 0) {
+ this.familiarFollowers = familiarFollowersList.get(0);
+ updateAccount();
+ }
+ });
+
//Retrieve identity proofs
accountsVM.getIdentityProofs(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id).observe(ProfileActivity.this, identityProofs -> {
this.identityProofList = identityProofs;
@@ -567,6 +578,27 @@ public class ProfileActivity extends BaseActivity {
}
}
+ if (familiarFollowers != null && familiarFollowers.accounts != null && familiarFollowers.accounts.size() > 0) {
+ binding.relatedAccounts.removeAllViews();
+ for (Account account : familiarFollowers.accounts) {
+ NotificationsRelatedAccountsBinding notificationsRelatedAccountsBinding = NotificationsRelatedAccountsBinding.inflate(LayoutInflater.from(ProfileActivity.this));
+ MastodonHelper.loadProfileMediaMastodonRound(ProfileActivity.this, notificationsRelatedAccountsBinding.profilePicture, account);
+ notificationsRelatedAccountsBinding.acc.setText(account.username);
+ notificationsRelatedAccountsBinding.relatedAccountContainer.setOnClickListener(v -> {
+ Intent intent = new Intent(ProfileActivity.this, ProfileActivity.class);
+ Bundle b = new Bundle();
+ b.putSerializable(Helper.ARG_ACCOUNT, account);
+ intent.putExtras(b);
+ ActivityOptionsCompat options = ActivityOptionsCompat
+ .makeSceneTransitionAnimation(ProfileActivity.this, notificationsRelatedAccountsBinding.profilePicture, getString(R.string.activity_porfile_pp));
+ // start the new activity
+ startActivity(intent, options.toBundle());
+ });
+ binding.relatedAccounts.addView(notificationsRelatedAccountsBinding.getRoot());
+ }
+ binding.familiarFollowers.setVisibility(View.VISIBLE);
+ }
+
binding.accountFollow.setEnabled(true);
//Visibility depending of the relationship
if (relationship != null) {
diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java
index 398c43dc..b7cba899 100644
--- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java
+++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java
@@ -18,6 +18,7 @@ package app.fedilab.android.client.endpoints;
import java.util.List;
import app.fedilab.android.client.entities.api.Account;
+import app.fedilab.android.client.entities.api.FamiliarFollowers;
import app.fedilab.android.client.entities.api.FeaturedTag;
import app.fedilab.android.client.entities.api.IdentityProof;
import app.fedilab.android.client.entities.api.MastodonList;
@@ -253,6 +254,13 @@ public interface MastodonAccountsService {
@Query("id[]") List ids
);
+ //Get familiar followers
+ @GET("accounts/familiar_followers ")
+ Call> getFamiliarFollowers(
+ @Header("Authorization") String token,
+ @Query("id[]") List ids
+ );
+
//Get search
@GET("accounts/search")
Call> searchAccounts(
@@ -409,4 +417,15 @@ public interface MastodonAccountsService {
@Header("Authorization") String token,
@Path("account_id") String account_id
);
+
+
+ //Get user suggestions
+ @GET("directory")
+ Call> getDirectory(
+ @Header("Authorization") String token,
+ @Query("offset") Integer offset,
+ @Query("limit") Integer limit,
+ @Query("order") String order,
+ @Query("local") Boolean local
+ );
}
diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java b/app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java
new file mode 100644
index 00000000..1d753c55
--- /dev/null
+++ b/app/src/main/java/app/fedilab/android/client/entities/api/FamiliarFollowers.java
@@ -0,0 +1,28 @@
+package app.fedilab.android.client.entities.api;
+/* Copyright 2022 Thomas Schneider
+ *
+ * This file is a part of Fedilab
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+ * Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with Fedilab; if not,
+ * see . */
+
+import com.google.gson.annotations.SerializedName;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class FamiliarFollowers implements Serializable {
+
+ @SerializedName("id")
+ public String id;
+ @SerializedName("accounts")
+ public List accounts;
+}
diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java b/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java
index e3c5fa66..c4196055 100644
--- a/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java
+++ b/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java
@@ -382,6 +382,8 @@ public class Timeline {
TREND_MESSAGE("TREND_MESSAGE"),
@SerializedName("ACCOUNT_SUGGESTION")
ACCOUNT_SUGGESTION("ACCOUNT_SUGGESTION"),
+ @SerializedName("ACCOUNT_DIRECTORY")
+ ACCOUNT_DIRECTORY("ACCOUNT_DIRECTORY"),
@SerializedName("PUBLIC_TREND_MESSAGE")
TREND_MESSAGE_PUBLIC("TREND_MESSAGE_PUBLIC"),
@SerializedName("STATUS_HISTORY")
diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java
index 2d31e605..e443b786 100644
--- a/app/src/main/java/app/fedilab/android/helper/Helper.java
+++ b/app/src/main/java/app/fedilab/android/helper/Helper.java
@@ -253,6 +253,8 @@ public class Helper {
public static final String ARG_WORK_ID = "ARG_WORK_ID";
public static final String ARG_LIST_ID = "ARG_LIST_ID";
public static final String ARG_SEARCH_KEYWORD = "ARG_SEARCH_KEYWORD";
+ public static final String ARG_DIRECTORY_ORDER = "ARG_DIRECTORY_ORDER";
+ public static final String ARG_DIRECTORY_LOCAL = "ARG_DIRECTORY_LOCAL";
public static final String ARG_SEARCH_TYPE = "ARG_SEARCH_TYPE";
public static final String ARG_SEARCH_KEYWORD_CACHE = "ARG_SEARCH_KEYWORD_CACHE";
public static final String ARG_VIEW_MODEL_KEY = "ARG_VIEW_MODEL_KEY";
@@ -1809,7 +1811,7 @@ public class Helper {
binding.aboutSupport.setVisibility(View.GONE);
binding.aboutSupportPaypal.setVisibility(View.GONE);
}
- binding.accountFollow.setImageResource(R.drawable.ic_baseline_person_add_24);
+ binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24);
binding.aboutSupport.setOnClickListener(v -> {
Intent intentLiberapay = new Intent(Intent.ACTION_VIEW);
intentLiberapay.setData(Uri.parse("https://liberapay.com/tom79"));
diff --git a/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java b/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java
index b86a9ecd..8212dc74 100644
--- a/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java
+++ b/app/src/main/java/app/fedilab/android/helper/MastodonHelper.java
@@ -40,6 +40,9 @@ import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
import com.google.gson.annotations.SerializedName;
import java.text.SimpleDateFormat;
@@ -268,6 +271,52 @@ public class MastodonHelper {
}
}
+ public static void loadProfileMediaMastodonRound(Activity activity, ImageView view, Account account) {
+ Context context = view.getContext();
+ SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
+ @DrawableRes int placeholder = R.drawable.ic_person;
+ if (Helper.isValidContextForGlide(activity != null ? activity : context)) {
+ if (account == null) {
+ Glide.with(activity != null ? activity : context)
+ .asDrawable()
+ .load(placeholder)
+ .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(16)))
+ .thumbnail(0.1f)
+ .placeholder(placeholder)
+ .into(view);
+ return;
+ }
+ String targetedUrl = disableGif ? account.avatar_static : account.avatar;
+ if (targetedUrl != null) {
+ if (disableGif || (!targetedUrl.endsWith(".gif"))) {
+ Glide.with(activity != null ? activity : context)
+ .asDrawable()
+ .load(targetedUrl)
+ .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
+ .thumbnail(0.1f)
+ .placeholder(placeholder)
+ .into(view);
+ } else {
+ Glide.with(activity != null ? activity : context)
+ .asGif()
+ .load(targetedUrl)
+ .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
+ .thumbnail(0.1f)
+ .placeholder(placeholder)
+ .into(view);
+ }
+ } else {
+ Glide.with(activity != null ? activity : context)
+ .asDrawable()
+ .load(placeholder)
+ .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
+ .thumbnail(0.1f)
+ .into(view);
+ }
+ }
+ }
+
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
*
diff --git a/app/src/main/java/app/fedilab/android/helper/MediaHelper.java b/app/src/main/java/app/fedilab/android/helper/MediaHelper.java
index b6f65f7c..1a60e74a 100644
--- a/app/src/main/java/app/fedilab/android/helper/MediaHelper.java
+++ b/app/src/main/java/app/fedilab/android/helper/MediaHelper.java
@@ -100,7 +100,10 @@ public class MediaHelper {
}
try {
String mime = getMimeType(url);
- final String fileName = URLUtil.guessFileName(url, null, null);
+ String fileName = URLUtil.guessFileName(url, null, null);
+ if (fileName.endsWith(".bin")) {
+ fileName = fileName.replace(".bin", ".mp4");
+ }
request.allowScanningByMediaScanner();
if (mime.toLowerCase().startsWith("video")) {
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_MOVIES, context.getString(R.string.app_name) + "/" + fileName);
@@ -134,9 +137,11 @@ public class MediaHelper {
.into(new CustomTarget() {
@Override
public void onResourceReady(@NotNull File file, Transition super File> transition) {
- final String fileName = URLUtil.guessFileName(url, null, null);
-
+ String fileName = URLUtil.guessFileName(url, null, null);
+ if (fileName.endsWith(".bin")) {
+ fileName = fileName.replace(".bin", ".jpg");
+ }
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File targeted_folder = new File(path, context.getString(R.string.app_name));
if (!targeted_folder.exists()) {
diff --git a/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java b/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java
index 7a39946d..4f92c1e8 100644
--- a/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java
+++ b/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java
@@ -464,7 +464,7 @@ public class PinnedTimelineHelper {
break;
case NITTER:
item.setIcon(R.drawable.nitter);
- if (pinnedTimeline.remoteInstance.displayName.trim().length() > 0) {
+ if (pinnedTimeline.remoteInstance.displayName != null && pinnedTimeline.remoteInstance.displayName.trim().length() > 0) {
item.setTitle(pinnedTimeline.remoteInstance.displayName);
} else {
item.setTitle(pinnedTimeline.remoteInstance.host);
diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java
index baa1a162..ca686c88 100644
--- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java
+++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java
@@ -163,6 +163,8 @@ public class StatusAdapter extends RecyclerView.Adapter
public static final int STATUS_FILTERED = 3;
public static final int STATUS_FILTERED_HIDE = 4;
public static final int STATUS_PIXELFED = 5;
+ private static float measuredWidth = -1;
+ private static float measuredWidthArt = -1;
private final List statusList;
private final boolean minified;
private final Timeline.TimeLineEnum timelineType;
@@ -171,10 +173,7 @@ public class StatusAdapter extends RecyclerView.Adapter
public FetchMoreCallBack fetchMoreCallBack;
private Context context;
private boolean visiblePixelfed;
-
private RecyclerView mRecyclerView;
- private static float measuredWidth = -1;
- private static float measuredWidthArt = -1;
public StatusAdapter(List statuses, Timeline.TimeLineEnum timelineType, boolean minified, boolean canBeFederated, boolean checkRemotely) {
this.statusList = statuses;
@@ -1288,7 +1287,9 @@ public class StatusAdapter extends RecyclerView.Adapter
}
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
//--- MEDIA ATTACHMENT ---
- if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0) {
+ boolean cardDisplayed = (statusToDeal.card != null && (display_card || statusToDeal.isFocused) && statusToDeal.quote_id == null);
+ if (statusToDeal.media_attachments != null && statusToDeal.media_attachments.size() > 0 && (!cardDisplayed || statusToDeal.media_attachments.size() > 1)) {
+
holder.binding.attachmentsList.removeAllViews();
holder.binding.mediaContainer.removeAllViews();
if ((loadMediaType.equals("ASK") || (loadMediaType.equals("WIFI") && !TimelineHelper.isOnWIFI(context))) && !statusToDeal.canLoadMedia) {
@@ -2246,53 +2247,6 @@ public class StatusAdapter extends RecyclerView.Adapter
}
- @NonNull
- @Override
- public List getPreloadItems(int position) {
- List 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 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
*
@@ -2317,21 +2271,6 @@ public class StatusAdapter extends RecyclerView.Adapter
LocalBroadcastManager.getInstance(context).sendBroadcast(intentBC);
}
- /* private static boolean mediaObfuscated(Status status) {
- //Media is not sensitive and doesn't have a spoiler text
- if (!status.isMediaObfuscated) {
- return false;
- }
- if (!status.sensitive && (status.spoiler_text == null || status.spoiler_text.trim().isEmpty())) {
- return false;
- }
- if (status.isMediaObfuscated && status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
- return true;
- } else {
- return status.sensitive;
- }
- }*/
-
public static void applyColor(Context context, StatusViewHolder holder) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
int currentNightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
@@ -2418,6 +2357,68 @@ public class StatusAdapter extends RecyclerView.Adapter
}
}
+ @NonNull
+ @Override
+ public List getPreloadItems(int position) {
+ List 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;
+ }
+
+ /* private static boolean mediaObfuscated(Status status) {
+ //Media is not sensitive and doesn't have a spoiler text
+ if (!status.isMediaObfuscated) {
+ return false;
+ }
+ if (!status.sensitive && (status.spoiler_text == null || status.spoiler_text.trim().isEmpty())) {
+ return false;
+ }
+ if (status.isMediaObfuscated && status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
+ return true;
+ } else {
+ return status.sensitive;
+ }
+ }*/
+
+ @Nullable
+ @Override
+ public RequestBuilder 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);
+ }
+
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
@@ -2615,7 +2616,7 @@ public class StatusAdapter extends RecyclerView.Adapter
holder.bindingArt.artMedia.setScaleType(ImageView.ScaleType.FIT_CENTER);
holder.bindingArt.artMedia.setLayoutParams(lp);
RequestBuilder requestBuilder = prepareRequestBuilder(context, status.art_attachment, mediaW * ratio, mediaH * ratio, 1.0f, 1.0f, status.sensitive, true);
- requestBuilder.into(holder.bindingArt.artMedia);
+ requestBuilder.load(status.art_attachment.preview_url).into(holder.bindingArt.artMedia);
}
}
diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java
index b71ef24c..e8270311 100644
--- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java
+++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java
@@ -15,6 +15,8 @@ package app.fedilab.android.ui.fragment.timeline;
* see . */
+import static app.fedilab.android.helper.MastodonHelper.ACCOUNTS_PER_CALL;
+
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -63,6 +65,8 @@ public class FragmentMastodonAccount extends Fragment {
private FedilabProfileTLPageAdapter.follow_type followType;
private String viewModelKey;
private Timeline.TimeLineEnum timelineType;
+ private String order;
+ private Boolean local;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
@@ -72,6 +76,8 @@ public class FragmentMastodonAccount extends Fragment {
followType = (FedilabProfileTLPageAdapter.follow_type) getArguments().getSerializable(Helper.ARG_FOLLOW_TYPE);
viewModelKey = getArguments().getString(Helper.ARG_VIEW_MODEL_KEY, "");
timelineType = (Timeline.TimeLineEnum) getArguments().get(Helper.ARG_TIMELINE_TYPE);
+ order = getArguments().getString(Helper.ARG_DIRECTORY_ORDER, "active");
+ local = getArguments().getBoolean(Helper.ARG_DIRECTORY_LOCAL, false);
}
flagLoading = false;
binding = FragmentPaginationBinding.inflate(inflater, container, false);
@@ -159,7 +165,15 @@ public class FragmentMastodonAccount extends Fragment {
.observe(getViewLifecycleOwner(), this::initializeAccountCommonView);
} else {
accountsVM.getBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.accountsPerCall(requireActivity())), max_id, null)
+ .observe(getViewLifecycleOwner(), this::dealWithPagination);
+ }
+ } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_DIRECTORY) {
+ if (firstLoad) {
+ accountsVM.getDirectory(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, 0, ACCOUNTS_PER_CALL, order, local)
.observe(getViewLifecycleOwner(), this::initializeAccountCommonView);
+ } else {
+ accountsVM.getDirectory(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, offset, ACCOUNTS_PER_CALL, order, local)
+ .observe(getViewLifecycleOwner(), this::dealWithPagination);
}
}
}
@@ -223,8 +237,10 @@ public class FragmentMastodonAccount extends Fragment {
this.accounts = accounts.accounts;
accountAdapter = new AccountAdapter(this.accounts, timelineType == Timeline.TimeLineEnum.MUTED_TIMELINE_HOME);
- if (search == null) {
+ if (search == null && timelineType != Timeline.TimeLineEnum.ACCOUNT_DIRECTORY) {
flagLoading = accounts.pagination.max_id == null;
+ } else if (timelineType != Timeline.TimeLineEnum.ACCOUNT_DIRECTORY) {
+ offset += ACCOUNTS_PER_CALL;
} else {
offset += MastodonHelper.SEARCH_PER_CALL;
}
@@ -288,6 +304,8 @@ public class FragmentMastodonAccount extends Fragment {
max_id = fetched_accounts.pagination.max_id;
if (search != null) {
offset += MastodonHelper.SEARCH_PER_CALL;
+ } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_DIRECTORY) {
+ offset += ACCOUNTS_PER_CALL;
}
accountAdapter.notifyItemRangeInserted(startId, fetched_accounts.accounts.size());
} else {
diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java
index d4a84a5f..03021530 100644
--- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java
+++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java
@@ -76,6 +76,7 @@ import es.dmoral.toasty.Toasty;
public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.FetchMoreCallBack {
+ private static final int PRELOAD_AHEAD_ITEMS = 10;
public UpdateCounters update;
private FragmentPaginationBinding binding;
private TimelinesVM timelinesVM;
@@ -88,8 +89,6 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
private StatusAdapter statusAdapter;
private Timeline.TimeLineEnum timelineType;
private List timelineStatuses;
- private static final int PRELOAD_AHEAD_ITEMS = 10;
- private ViewPreloadSizeProvider preloadSizeProvider;
//Handle actions that can be done in other fragments
private final BroadcastReceiver receive_action = new BroadcastReceiver() {
@Override
@@ -179,6 +178,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter.
}
}
};
+ private ViewPreloadSizeProvider preloadSizeProvider;
private boolean checkRemotely;
private String accountIDInRemoteInstance;
private boolean isViewInitialized;
diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java
index fbf37746..1567a502 100644
--- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java
+++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java
@@ -38,6 +38,7 @@ import app.fedilab.android.client.endpoints.MastodonAccountsService;
import app.fedilab.android.client.entities.api.Account;
import app.fedilab.android.client.entities.api.Accounts;
import app.fedilab.android.client.entities.api.Domains;
+import app.fedilab.android.client.entities.api.FamiliarFollowers;
import app.fedilab.android.client.entities.api.FeaturedTag;
import app.fedilab.android.client.entities.api.Field;
import app.fedilab.android.client.entities.api.Filter;
@@ -90,6 +91,7 @@ public class AccountsVM extends AndroidViewModel {
private MutableLiveData> identityProofListMutableLiveData;
private MutableLiveData relationShipMutableLiveData;
private MutableLiveData> relationShipListMutableLiveData;
+ private MutableLiveData> familiarFollowersListMutableLiveData;
private MutableLiveData filterMutableLiveData;
private MutableLiveData> filterListMutableLiveData;
private MutableLiveData> tagListMutableLiveData;
@@ -1025,6 +1027,38 @@ public class AccountsVM extends AndroidViewModel {
return relationShipListMutableLiveData;
}
+
+ /**
+ * Obtain a list of all accounts that follow a given account, filtered for accounts you follow.
+ *
+ * @param ids {@link List} of account IDs to check
+ * @return {@link LiveData} containing a {@link List} of {@link FamiliarFollowers}s to given account(s)
+ */
+ public LiveData> getFamiliarFollowers(@NonNull String instance, String token, @NonNull List ids) {
+ familiarFollowersListMutableLiveData = new MutableLiveData<>();
+ MastodonAccountsService mastodonAccountsService = init(instance);
+ new Thread(() -> {
+ List familiarFollowers = null;
+ Call> familiarFollowersCall = mastodonAccountsService.getFamiliarFollowers(token, ids);
+
+ if (familiarFollowersCall != null) {
+ try {
+ Response> familiarFollowersResponse = familiarFollowersCall.execute();
+ if (familiarFollowersResponse.isSuccessful()) {
+ familiarFollowers = familiarFollowersResponse.body();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ List finalFamiliarFollowers = familiarFollowers;
+ Runnable myRunnable = () -> familiarFollowersListMutableLiveData.setValue(finalFamiliarFollowers);
+ mainHandler.post(myRunnable);
+ }).start();
+ return familiarFollowersListMutableLiveData;
+ }
+
/**
* Search for matching accounts by username or display name.
*
@@ -1561,7 +1595,7 @@ public class AccountsVM extends AndroidViewModel {
* Accounts the user has had past positive interactions with, but is not yet following.
*
* @param limit Maximum number of results to return. Defaults to 40.
- * @return {@link LiveData} containing a {@link List} of {@link Account}s
+ * @return {@link LiveData} containing a {@link List} of {@link Suggestion}s
*/
public LiveData getSuggestions(@NonNull String instance, String token, String limit) {
suggestionsMutableLiveData = new MutableLiveData<>();
@@ -1587,6 +1621,37 @@ public class AccountsVM extends AndroidViewModel {
return suggestionsMutableLiveData;
}
+ /**
+ * List accounts visible in the directory.
+ *
+ * @param limit Maximum number of results to return. Defaults to 40.
+ * @return {@link LiveData} containing a {@link List} of {@link Account}s
+ */
+ public LiveData getDirectory(@NonNull String instance, String token, Integer offset, Integer limit, String order, Boolean local) {
+ accountsMutableLiveData = new MutableLiveData<>();
+ MastodonAccountsService mastodonAccountsService = init(instance);
+ new Thread(() -> {
+ Call> accountsCall = mastodonAccountsService.getDirectory(token, offset, limit, order, local);
+ Accounts accounts = new Accounts();
+
+ if (accountsCall != null) {
+ try {
+ Response> directoryResponse = accountsCall.execute();
+ if (directoryResponse.isSuccessful()) {
+ accounts.pagination = MastodonHelper.getOffSetPagination(directoryResponse.headers());
+ accounts.accounts = directoryResponse.body();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ Runnable myRunnable = () -> accountsMutableLiveData.setValue(accounts);
+ mainHandler.post(myRunnable);
+ }).start();
+ return accountsMutableLiveData;
+ }
+
/**
* Remove an account from follow suggestions.
*
diff --git a/app/src/main/res/drawable/ic_baseline_android_24.xml b/app/src/main/res/drawable/ic_baseline_android_24.xml
new file mode 100644
index 00000000..b812e1ed
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_android_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_perm_contact_calendar_24.xml b/app/src/main/res/drawable/ic_baseline_perm_contact_calendar_24.xml
new file mode 100644
index 00000000..ea5ff25a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_perm_contact_calendar_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_directory.xml b/app/src/main/res/layout/activity_directory.xml
new file mode 100644
index 00000000..c5653185
--- /dev/null
+++ b/app/src/main/res/layout/activity_directory.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml
index f98dcda6..79f8910c 100644
--- a/app/src/main/res/layout/activity_profile.xml
+++ b/app/src/main/res/layout/activity_profile.xml
@@ -341,6 +341,44 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info" />
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -147,8 +130,31 @@
+
+
+
+
-
-
+
-
+
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 67ecae10..d4df276b 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -973,4 +973,7 @@
Ztišen(a)
Kompaktní tlačítka akcí
Tlačítka v dolní části zpráv nezaberou celou šířku
+ Viditelnost odpovědí
+ Viditelnost vyloučení
+ Sledován(a):
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 2e549a74..6f2dbf56 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -13,7 +13,7 @@
Baixar
Baixar %1$s
Mídia salva
- Arquivo: %1$s
+ Ficheiro: %1$s
Senha
E-mail
Contas
@@ -547,7 +547,7 @@
Toque aqui para importar um tema exportado previamente
Exportar o tema
Toque aqui para exportar o tema atual
- Ocorreu um erro ao selecionar o arquivo do tema
+ Ocorreu um erro ao selecionar o ficheiro do tema
Número de usuários
Número de status
Número de instâncias
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e83cc5cc..3567f944 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -486,6 +486,7 @@
Unresolved
Remote
Active
+ New
Pending
Disabled
Suspended
@@ -2205,4 +2206,6 @@
Pixelfed presentation for media
Compact action buttons
Buttons at the bottom of messages will not take the whole width
+ Followed by:
+ Directory
\ No newline at end of file
diff --git a/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt b/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt
new file mode 100644
index 00000000..b35d2e68
--- /dev/null
+++ b/src/fdroid/fastlane/metadata/android/en/changelogs/464.txt
@@ -0,0 +1,12 @@
+Added:
+- Display familiar followers on profiles
+- Display and filter Instance directory
+
+Changed:
+- Bot and reply icon indicators more visible
+- Single media are hidden with link previews
+
+Fixed:
+- Fix a crash with Art timelines
+- Friendica: media cannot be downloaded/shared
+- Fix a crash with pinned timelines
\ No newline at end of file