Filter timelines locally (hide boosts/replies and filter with regex)

This commit is contained in:
Thomas 2022-05-10 17:21:22 +02:00
parent 87a5b69ba0
commit 8fcbe19d4d
12 changed files with 398 additions and 42 deletions

View file

@ -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();

View file

@ -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) {

View file

@ -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<Status> 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<Filter> filterList;
Call<List<Filter>> getFiltersCall = mastodonAccountsService.getFilters(MainActivity.currentToken);
if (getFiltersCall != null) {
try {
Response<List<Filter>> 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) {

View file

@ -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<RecyclerView.ViewHolder
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
StatusAdapter.StatusViewHolder holder = (StatusAdapter.StatusViewHolder) viewHolder;
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, false, false);
statusManagement(context, statusesVM, searchVM, holder, this, statusList, null, status, Timeline.TimeLineEnum.UNKNOWN, false);
//Hide/Show specific view
}

View file

@ -36,6 +36,7 @@ import java.util.Locale;
import app.fedilab.android.R;
import app.fedilab.android.activities.ProfileActivity;
import app.fedilab.android.client.entities.Timeline;
import app.fedilab.android.client.mastodon.entities.Notification;
import app.fedilab.android.databinding.DrawerFollowBinding;
import app.fedilab.android.databinding.DrawerStatusNotificationBinding;
@ -134,7 +135,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
StatusAdapter.StatusViewHolder holderStatus = (StatusAdapter.StatusViewHolder) viewHolder;
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
statusManagement(context, statusesVM, searchVM, holderStatus, this, null, notificationList, notification.status, false, false);
statusManagement(context, statusesVM, searchVM, holderStatus, this, null, notificationList, notification.status, Timeline.TimeLineEnum.NOTIFICATION, false);
holderStatus.bindingNotification.containerTransparent.setAlpha(.3f);
if (getItemViewType(position) == TYPE_MENTION || getItemViewType(position) == TYPE_STATUS) {
holderStatus.bindingNotification.status.actionButtons.setVisibility(View.VISIBLE);

View file

@ -15,6 +15,11 @@ package app.fedilab.android.ui.drawer;
* see <http://www.gnu.org/licenses>. */
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<RecyclerView.ViewHolder> {
private final List<Status> statusList;
private final boolean remote;
private final boolean minified;
private Context context;
private final Timeline.TimeLineEnum timelineType;
public StatusAdapter(List<Status> statuses, boolean remote, boolean minified) {
public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified) {
this.statusList = statuses;
this.remote = remote;
this.timelineType = timelineType;
this.minified = minified;
}
public StatusAdapter(List<Status> 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<RecyclerView.ViewHolder> - General adapter that can be for {@link StatusAdapter} or {@link NotificationAdapter}
* @param statusList List<Status>
* @param notificationList List<Notification>
* @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<RecyclerView.ViewHolder>
List<Status> statusList,
List<Notification> 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<RecyclerView.ViewHolder>
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<RecyclerView.ViewHolder>
@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<RecyclerView.ViewHolder>
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<RecyclerView.ViewHolder>
binding = itemView.status;
}
StatusViewHolder(DrawerStatusHiddenBinding itemView) {
super(itemView.getRoot());
bindingHidden = itemView;
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1dp"
android:visibility="gone" />

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/filter_regex"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/filter_regex"
android:inputType="text"
android:singleLine="true" />
</androidx.appcompat.widget.LinearLayoutCompat>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_show_boosts"
android:checkable="true"
android:title="@string/show_boosts"
app:actionViewClass="android.widget.CheckBox"
app:showAsAction="always"
tools:ignore="AlwaysShowAction" />
<item
android:id="@+id/action_show_replies"
android:checkable="true"
android:title="@string/show_replies"
app:actionViewClass="android.widget.CheckBox"
app:showAsAction="always" />
<item
android:id="@+id/action_filter"
android:title="@string/filter_regex"
app:showAsAction="always" />
</menu>