From 8fcbe19d4deec22c8e3e62b1c9d515b5b4f8491a Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 10 May 2022 17:21:22 +0200 Subject: [PATCH] Filter timelines locally (hide boosts/replies and filter with regex) --- .../app/fedilab/android/BaseMainActivity.java | 211 +++++++++++++++++- .../android/client/entities/Timeline.java | 6 +- .../android/helper/TimelineHelper.java | 47 +++- .../android/ui/drawer/ContextAdapter.java | 3 +- .../ui/drawer/NotificationAdapter.java | 3 +- .../android/ui/drawer/StatusAdapter.java | 103 +++++++-- .../ui/fragment/media/FragmentMedia.java | 4 +- .../timeline/FragmentMastodonContext.java | 3 +- .../timeline/FragmentMastodonTimeline.java | 19 +- .../main/res/layout/drawer_status_hidden.xml | 5 + .../main/res/layout/popup_filter_regex.xml | 14 ++ app/src/main/res/menu/option_filter_toots.xml | 22 ++ 12 files changed, 398 insertions(+), 42 deletions(-) create mode 100644 app/src/main/res/layout/drawer_status_hidden.xml create mode 100644 app/src/main/res/layout/popup_filter_regex.xml create mode 100644 app/src/main/res/menu/option_filter_toots.xml diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index e4eb1c6d7..97d233fcf 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -31,15 +31,21 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.text.Editable; +import android.text.TextWatcher; import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -69,6 +75,7 @@ import java.io.File; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; +import java.util.regex.Pattern; import app.fedilab.android.activities.ActionActivity; import app.fedilab.android.activities.BaseActivity; @@ -100,6 +107,8 @@ import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.PinnedTimelineHelper; import app.fedilab.android.helper.PushHelper; import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonConversation; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonNotification; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline; import app.fedilab.android.viewmodel.mastodon.AccountsVM; import app.fedilab.android.viewmodel.mastodon.InstancesVM; @@ -123,7 +132,8 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt private AppBarConfiguration mAppBarConfiguration; private ActivityMainBinding binding; private Pinned pinned; - + public static boolean show_boosts, show_replies, show_art_nsfw; + public static String regex_home, regex_local, regex_public; private final BroadcastReceiver broadcast_data = new BroadcastReceiver() { @Override @@ -221,18 +231,53 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt binding.bottomNavView.inflateMenu(R.menu.bottom_nav_menu); binding.bottomNavView.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); binding.navView.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); + + + //ManageClick on bottom menu items + binding.bottomNavView.findViewById(R.id.nav_home).setOnLongClickListener(view -> { + manageFilters(0); + return false; + }); + binding.bottomNavView.findViewById(R.id.nav_local).setOnLongClickListener(view -> { + manageFilters(1); + return false; + }); + binding.bottomNavView.findViewById(R.id.nav_public).setOnLongClickListener(view -> { + manageFilters(2); + return false; + }); binding.bottomNavView.setOnItemSelectedListener(item -> { int itemId = item.getItemId(); if (itemId == R.id.nav_home) { - binding.viewPager.setCurrentItem(0); + if (binding.viewPager.getCurrentItem() == 0) { + scrollToTop(); + } else { + binding.viewPager.setCurrentItem(0); + } } else if (itemId == R.id.nav_local) { - binding.viewPager.setCurrentItem(1); + if (binding.viewPager.getCurrentItem() == 1) { + scrollToTop(); + } else { + binding.viewPager.setCurrentItem(1); + } } else if (itemId == R.id.nav_public) { - binding.viewPager.setCurrentItem(2); + if (binding.viewPager.getCurrentItem() == 2) { + scrollToTop(); + } else { + binding.viewPager.setCurrentItem(2); + } } else if (itemId == R.id.nav_notifications) { - binding.viewPager.setCurrentItem(3); + if (binding.viewPager.getCurrentItem() == 3) { + scrollToTop(); + } else { + binding.viewPager.setCurrentItem(3); + } } else if (itemId == R.id.nav_privates) { - binding.viewPager.setCurrentItem(4); + if (binding.viewPager.getCurrentItem() == 4) { + scrollToTop(); + } else { + binding.viewPager.setCurrentItem(4); + } } return true; }); @@ -501,6 +546,12 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } currentInstance = account.instance; currentUserID = account.user_id; + show_boosts = sharedpreferences.getBoolean(getString(R.string.SET_SHOW_BOOSTS) + currentUserID + currentInstance, true); + show_replies = sharedpreferences.getBoolean(getString(R.string.SET_SHOW_REPLIES) + currentUserID + currentInstance, true); + regex_home = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_HOME) + currentUserID + currentInstance, null); + regex_local = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_LOCAL) + currentUserID + currentInstance, null); + regex_public = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_PUBLIC) + currentUserID + currentInstance, null); + show_art_nsfw = sharedpreferences.getBoolean(getString(R.string.SET_ART_WITH_NSFW) + currentUserID + currentInstance, false); accountWeakReference = new WeakReference<>(account); binding.profilePicture.setOnClickListener(v -> binding.drawerLayout.openDrawer(GravityCompat.START)); Helper.loadPP(binding.profilePicture, account); @@ -579,6 +630,134 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt LocalBroadcastManager.getInstance(BaseMainActivity.this).registerReceiver(broadcast_data, new IntentFilter(Helper.BROADCAST_DATA)); } + + private void manageFilters(int position) { + View view = binding.bottomNavView.findViewById(R.id.nav_home); + if (position == 1) { + view = binding.bottomNavView.findViewById(R.id.nav_local); + } else if (position == 2) { + view = binding.bottomNavView.findViewById(R.id.nav_public); + } + PopupMenu popup = new PopupMenu(new ContextThemeWrapper(BaseMainActivity.this, Helper.popupStyle()), view, Gravity.TOP); + popup.getMenuInflater() + .inflate(R.menu.option_filter_toots, popup.getMenu()); + Menu menu = popup.getMenu(); + final MenuItem itemShowBoosts = menu.findItem(R.id.action_show_boosts); + final MenuItem itemShowReplies = menu.findItem(R.id.action_show_replies); + final MenuItem itemFilter = menu.findItem(R.id.action_filter); + if (position > 0) { + itemShowBoosts.setVisible(false); + itemShowReplies.setVisible(false); + } else { + itemShowBoosts.setVisible(true); + itemShowReplies.setVisible(true); + } + + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(BaseMainActivity.this); + String show_filtered = null; + if (position == 0) { + show_filtered = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_HOME) + currentUserID + currentInstance, null); + } else if (position == 1) { + show_filtered = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_LOCAL) + currentUserID + currentInstance, null); + } else if (position == 2) { + show_filtered = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_PUBLIC) + currentUserID + currentInstance, null); + } + itemShowBoosts.setChecked(show_boosts); + itemShowReplies.setChecked(show_replies); + if (show_filtered != null && show_filtered.length() > 0) { + itemFilter.setTitle(show_filtered); + } + popup.setOnDismissListener(menu1 -> { + if (binding.viewPager.getAdapter() != null) { + Fragment fragment = (Fragment) binding.viewPager.getAdapter().instantiateItem(binding.viewPager, binding.tabLayout.getSelectedTabPosition()); + if (fragment instanceof FragmentMastodonTimeline && fragment.isVisible()) { + FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); + fragmentMastodonTimeline.refreshAllAdapters(); + } + } + }); + String finalShow_filtered = show_filtered; + popup.setOnMenuItemClickListener(item -> { + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + item.setActionView(new View(BaseMainActivity.this)); + item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return false; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + return false; + } + }); + final SharedPreferences.Editor editor = sharedpreferences.edit(); + int itemId = item.getItemId(); + if (itemId == R.id.action_show_boosts) { + show_boosts = !show_boosts; + editor.putBoolean(getString(R.string.SET_SHOW_BOOSTS) + currentUserID + currentInstance, show_boosts); + itemShowBoosts.setChecked(show_boosts); + editor.apply(); + } else if (itemId == R.id.action_show_replies) { + show_replies = !show_replies; + editor.putBoolean(getString(R.string.SET_SHOW_REPLIES) + currentUserID + currentInstance, show_replies); + itemShowReplies.setChecked(show_replies); + editor.apply(); + } else if (itemId == R.id.action_filter) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(BaseMainActivity.this, Helper.dialogStyle()); + LayoutInflater inflater = getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.popup_filter_regex, new LinearLayout(BaseMainActivity.this), false); + dialogBuilder.setView(dialogView); + final EditText editText = dialogView.findViewById(R.id.filter_regex); + Toast alertRegex = Toasty.warning(BaseMainActivity.this, getString(R.string.alert_regex), Toast.LENGTH_LONG); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + try { + Pattern.compile("(" + s.toString() + ")", Pattern.CASE_INSENSITIVE); + } catch (Exception e) { + if (!alertRegex.getView().isShown()) { + alertRegex.show(); + } + } + + } + }); + if (finalShow_filtered != null) { + editText.setText(finalShow_filtered); + editText.setSelection(editText.getText().toString().length()); + } + dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { + itemFilter.setTitle(editText.getText().toString().trim()); + if (position == 0) { + editor.putString(getString(R.string.SET_FILTER_REGEX_HOME) + currentUserID + currentInstance, editText.getText().toString().trim()); + regex_home = editText.getText().toString().trim(); + } else if (position == 1) { + editor.putString(getString(R.string.SET_FILTER_REGEX_LOCAL) + currentUserID + currentInstance, editText.getText().toString().trim()); + regex_local = editText.getText().toString().trim(); + } else if (position == 2) { + editor.putString(getString(R.string.SET_FILTER_REGEX_PUBLIC) + currentUserID + currentInstance, editText.getText().toString().trim()); + regex_public = editText.getText().toString().trim(); + } + editor.apply(); + }); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + return true; + } + return false; + }); + popup.show(); + } + public void refreshFragment() { if (binding.viewPager.getAdapter() != null) { binding.viewPager.getAdapter().notifyDataSetChanged(); @@ -615,6 +794,26 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt super.onDestroy(); } + /** + * Allow to scroll to top for bottom navigation items + */ + private void scrollToTop() { + if (binding.viewPager.getAdapter() != null) { + Fragment fragment = (Fragment) binding.viewPager.getAdapter().instantiateItem(binding.viewPager, binding.tabLayout.getSelectedTabPosition()); + if (fragment instanceof FragmentMastodonTimeline) { + FragmentMastodonTimeline fragmentMastodonTimeline = ((FragmentMastodonTimeline) fragment); + fragmentMastodonTimeline.scrollToTop(); + } else if (fragment instanceof FragmentMastodonNotification) { + FragmentMastodonNotification fragmentMastodonNotification = ((FragmentMastodonNotification) fragment); + fragmentMastodonNotification.scrollToTop(); + } else if (fragment instanceof FragmentMastodonConversation) { + FragmentMastodonConversation fragmentMastodonConversation = ((FragmentMastodonConversation) fragment); + fragmentMastodonConversation.scrollToTop(); + } + } + } + + @Override protected void onResume() { super.onResume(); diff --git a/app/src/main/java/app/fedilab/android/client/entities/Timeline.java b/app/src/main/java/app/fedilab/android/client/entities/Timeline.java index bd610973a..35b0eaa2f 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/Timeline.java +++ b/app/src/main/java/app/fedilab/android/client/entities/Timeline.java @@ -385,9 +385,9 @@ public class Timeline { @SerializedName("SCHEDULED_TOOT_CLIENT") SCHEDULED_TOOT_CLIENT("SCHEDULED_TOOT_CLIENT"), @SerializedName("SCHEDULED_BOOST") - SCHEDULED_BOOST("SCHEDULED_BOOST"); - - + SCHEDULED_BOOST("SCHEDULED_BOOST"), + @SerializedName("UNKNOWN") + UNKNOWN("UNKNOWN"); private final String value; TimeLineEnum(String value) { diff --git a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java index edc3ebd6d..6fee1df45 100644 --- a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java @@ -24,19 +24,42 @@ import androidx.lifecycle.ViewModelStoreOwner; import com.google.gson.annotations.SerializedName; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.mastodon.MastodonAccountsService; import app.fedilab.android.client.mastodon.entities.Filter; import app.fedilab.android.client.mastodon.entities.Notification; import app.fedilab.android.client.mastodon.entities.Status; import app.fedilab.android.viewmodel.mastodon.AccountsVM; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; public class TimelineHelper { + private static MastodonAccountsService init(Context context) { + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .readTimeout(60, TimeUnit.SECONDS) + .connectTimeout(60, TimeUnit.SECONDS) + .proxy(Helper.getProxy(context)) + .build(); + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + MainActivity.currentInstance + "/api/v1/") + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build(); + return retrofit.create(MastodonAccountsService.class); + } + /** * Allows to filter statuses, should be called in API calls (background) * @@ -49,17 +72,23 @@ public class TimelineHelper { //A security to make sure filters have been fetched before displaying messages List statusesToRemove = new ArrayList<>(); if (!BaseMainActivity.filterFetched) { - try { - AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class); - accountsVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken).observe((LifecycleOwner) context, filters -> { - BaseMainActivity.filterFetched = true; - BaseMainActivity.mainFilters = filters; - }); - } catch (ClassCastException e) { - e.printStackTrace(); - return statuses; + MastodonAccountsService mastodonAccountsService = init(context); + List filterList; + Call> getFiltersCall = mastodonAccountsService.getFilters(MainActivity.currentToken); + if (getFiltersCall != null) { + try { + Response> getFiltersResponse = getFiltersCall.execute(); + if (getFiltersResponse.isSuccessful()) { + BaseMainActivity.filterFetched = true; + filterList = getFiltersResponse.body(); + BaseMainActivity.mainFilters = filterList; + } + } catch (IOException e) { + e.printStackTrace(); + } } } + //If there are filters: if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0) { for (Filter filter : BaseMainActivity.mainFilters) { diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ContextAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ContextAdapter.java index 79fa33293..639d56bc0 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ContextAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ContextAdapter.java @@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; +import app.fedilab.android.client.entities.Timeline; import app.fedilab.android.client.mastodon.entities.Status; import app.fedilab.android.databinding.DrawerStatusBinding; import app.fedilab.android.viewmodel.mastodon.SearchVM; @@ -71,7 +72,7 @@ public class ContextAdapter extends RecyclerView.Adapter. */ +import static app.fedilab.android.BaseMainActivity.regex_home; +import static app.fedilab.android.BaseMainActivity.regex_local; +import static app.fedilab.android.BaseMainActivity.regex_public; +import static app.fedilab.android.BaseMainActivity.show_boosts; +import static app.fedilab.android.BaseMainActivity.show_replies; import static app.fedilab.android.activities.ContextActivity.expand; import android.annotation.SuppressLint; @@ -96,11 +101,13 @@ import app.fedilab.android.activities.ProfileActivity; import app.fedilab.android.activities.ReportActivity; import app.fedilab.android.activities.StatusInfoActivity; import app.fedilab.android.client.entities.StatusDraft; +import app.fedilab.android.client.entities.Timeline; import app.fedilab.android.client.mastodon.entities.Attachment; import app.fedilab.android.client.mastodon.entities.Notification; import app.fedilab.android.client.mastodon.entities.Poll; import app.fedilab.android.client.mastodon.entities.Status; import app.fedilab.android.databinding.DrawerStatusBinding; +import app.fedilab.android.databinding.DrawerStatusHiddenBinding; import app.fedilab.android.databinding.DrawerStatusNotificationBinding; import app.fedilab.android.databinding.DrawerStatusReportBinding; import app.fedilab.android.databinding.LayoutMediaBinding; @@ -120,35 +127,75 @@ import jp.wasabeef.glide.transformations.BlurTransformation; public class StatusAdapter extends RecyclerView.Adapter { private final List statusList; - private final boolean remote; private final boolean minified; private Context context; + private final Timeline.TimeLineEnum timelineType; - public StatusAdapter(List statuses, boolean remote, boolean minified) { + public StatusAdapter(List statuses, Timeline.TimeLineEnum timelineType, boolean minified) { this.statusList = statuses; - this.remote = remote; + this.timelineType = timelineType; this.minified = minified; } - public StatusAdapter(List statuses, boolean remote) { - this.statusList = statuses; - this.remote = remote; - this.minified = false; + private static boolean isVisble(Timeline.TimeLineEnum timelineType, Status status) { + if (timelineType == Timeline.TimeLineEnum.HOME && !show_boosts && status.reblog != null) { + return false; + } + if (timelineType == Timeline.TimeLineEnum.HOME && !show_replies && status.in_reply_to_id != null) { + return false; + } + if (timelineType == Timeline.TimeLineEnum.HOME && regex_home != null && !regex_home.trim().equals("")) { + try { + Pattern filterPattern = Pattern.compile("(" + regex_home + ")", Pattern.CASE_INSENSITIVE); + Matcher matcher = filterPattern.matcher(status.content); + if (matcher.find()) + return false; + matcher = filterPattern.matcher(status.spoiler_text); + if (matcher.find()) + return false; + } catch (Exception ignored) { + } + } + if (timelineType == Timeline.TimeLineEnum.LOCAL && regex_local != null && !regex_local.trim().equals("")) { + try { + Pattern filterPattern = Pattern.compile("(" + regex_local + ")", Pattern.CASE_INSENSITIVE); + Matcher matcher = filterPattern.matcher(status.content); + if (matcher.find()) + return false; + matcher = filterPattern.matcher(status.spoiler_text); + if (matcher.find()) + return false; + } catch (Exception ignored) { + } + } + if (timelineType == Timeline.TimeLineEnum.PUBLIC && regex_public != null && !regex_public.trim().equals("")) { + try { + Pattern filterPattern = Pattern.compile("(" + regex_public + ")", Pattern.CASE_INSENSITIVE); + Matcher matcher = filterPattern.matcher(status.content); + if (matcher.find()) + return false; + matcher = filterPattern.matcher(status.spoiler_text); + if (matcher.find()) + return false; + } catch (Exception ignored) { + } + } + return true; } - /** * Manage status, this method is also reused in notifications timelines * * @param context Context + * @param context Timeline.TimeLineEnum timelineType * @param statusesVM StatusesVM - For handling actions in background to the correct activity * @param searchVM SearchVM - For handling remote actions * @param holder StatusViewHolder * @param adapter RecyclerView.Adapter - General adapter that can be for {@link StatusAdapter} or {@link NotificationAdapter} * @param statusList List * @param notificationList List - * @param remote boolean Indicate if the status is a remote one (ie not yet federated) + * @param timelineType Timeline.TimeLineEnum * @param status {@link Status} */ @SuppressLint("ClickableViewAccessibility") @@ -160,12 +207,17 @@ public class StatusAdapter extends RecyclerView.Adapter List statusList, List notificationList, Status status, - boolean remote, + Timeline.TimeLineEnum timelineType, boolean minified) { + if (status == null) { + return; + } + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + + boolean remote = timelineType == Timeline.TimeLineEnum.REMOTE; Status statusToDeal = status.reblog != null ? status.reblog : status; - final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); boolean expand_cw = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_CW), false); boolean expand_media = sharedpreferences.getBoolean(context.getString(R.string.SET_EXPAND_MEDIA), false); @@ -1491,16 +1543,26 @@ public class StatusAdapter extends RecyclerView.Adapter return position; } + @Override + public int getItemViewType(int position) { + return isVisble(timelineType, statusList.get(position)) ? 1 : 0; + } + @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { context = parent.getContext(); - if (!minified) { - DrawerStatusBinding itemBinding = DrawerStatusBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + if (viewType == 0) { + DrawerStatusHiddenBinding itemBinding = DrawerStatusHiddenBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); return new StatusViewHolder(itemBinding); } else { - DrawerStatusReportBinding itemBinding = DrawerStatusReportBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); - return new StatusViewHolder(itemBinding); + if (!minified) { + DrawerStatusBinding itemBinding = DrawerStatusBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new StatusViewHolder(itemBinding); + } else { + DrawerStatusReportBinding itemBinding = DrawerStatusReportBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new StatusViewHolder(itemBinding); + } } } @@ -1519,11 +1581,14 @@ public class StatusAdapter extends RecyclerView.Adapter @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + if (viewHolder.getItemViewType() == 0) { + return; + } Status status = statusList.get(position); StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class); SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class); - statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, remote, minified); + statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, timelineType, minified); if (holder.timer != null) { holder.timer.cancel(); holder.timer = null; @@ -1557,6 +1622,7 @@ public class StatusAdapter extends RecyclerView.Adapter public static class StatusViewHolder extends RecyclerView.ViewHolder { DrawerStatusBinding binding; + DrawerStatusHiddenBinding bindingHidden; DrawerStatusReportBinding bindingReport; DrawerStatusNotificationBinding bindingNotification; Timer timer; @@ -1578,6 +1644,11 @@ public class StatusAdapter extends RecyclerView.Adapter binding = itemView.status; } + StatusViewHolder(DrawerStatusHiddenBinding itemView) { + super(itemView.getRoot()); + bindingHidden = itemView; + } + } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java b/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java index f6e0a57a4..2ef2be7ed 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java @@ -93,8 +93,10 @@ public class FragmentMedia extends Fragment { url = attachment.url; binding.mediaPicture.setOnMatrixChangeListener(rect -> { + if (binding == null) { + return; + } canSwipe = (binding.mediaPicture.getScale() == 1); - if (!canSwipe) { if (!((MediaActivity) requireActivity()).getFullScreen()) { ((MediaActivity) requireActivity()).setFullscreen(true); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java index fc993614d..6b4332a47 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonContext.java @@ -39,6 +39,7 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.activities.ContextActivity; +import app.fedilab.android.client.entities.Timeline; import app.fedilab.android.client.mastodon.entities.Context; import app.fedilab.android.client.mastodon.entities.Status; import app.fedilab.android.databinding.FragmentPaginationBinding; @@ -171,7 +172,7 @@ public class FragmentMastodonContext extends Fragment { this.statuses = new ArrayList<>(); focusedStatus.isFocused = true; this.statuses.add(focusedStatus); - statusAdapter = new StatusAdapter(this.statuses, false); + statusAdapter = new StatusAdapter(this.statuses, Timeline.TimeLineEnum.UNKNOWN, false); binding.swipeContainer.setRefreshing(false); LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); binding.recyclerView.setLayoutManager(mLayoutManager); 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 12cfae6f8..9ac7338f2 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 @@ -276,7 +276,7 @@ public class FragmentMastodonTimeline extends Fragment { min_id = statuses.pagination.min_id; } - statusAdapter = new StatusAdapter(this.statuses, timelineType == Timeline.TimeLineEnum.REMOTE, minified); + statusAdapter = new StatusAdapter(this.statuses, timelineType, minified); if (statusReport != null) { scrollToTop(); @@ -426,11 +426,12 @@ public class FragmentMastodonTimeline extends Fragment { * @param direction - DIRECTION null if first call, then is set to TOP or BOTTOM depending of scroll */ private void route(DIRECTION direction) { - if (binding == null) { - return; - } + new Thread(() -> { QuickLoad quickLoad = new QuickLoad(requireActivity()).getSavedValue(timelineType, ident); + if (binding == null) { + return; + } if (!binding.swipeContainer.isRefreshing() && direction == null && quickLoad != null && quickLoad.statuses != null && quickLoad.statuses.size() > 0) { Statuses statuses = new Statuses(); statuses.statuses = quickLoad.statuses; @@ -654,6 +655,16 @@ public class FragmentMastodonTimeline extends Fragment { } } + + /** + * Refresh status in list + */ + public void refreshAllAdapters() { + if (statusAdapter != null && statuses != null) { + statusAdapter.notifyItemRangeChanged(0, statuses.size()); + } + } + public enum DIRECTION { TOP, BOTTOM diff --git a/app/src/main/res/layout/drawer_status_hidden.xml b/app/src/main/res/layout/drawer_status_hidden.xml new file mode 100644 index 000000000..ac9c9f6d0 --- /dev/null +++ b/app/src/main/res/layout/drawer_status_hidden.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_filter_regex.xml b/app/src/main/res/layout/popup_filter_regex.xml new file mode 100644 index 000000000..9566aa746 --- /dev/null +++ b/app/src/main/res/layout/popup_filter_regex.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/option_filter_toots.xml b/app/src/main/res/menu/option_filter_toots.xml new file mode 100644 index 000000000..c5be13513 --- /dev/null +++ b/app/src/main/res/menu/option_filter_toots.xml @@ -0,0 +1,22 @@ + + + + + +