Commit 1afa9822 authored by Thomas's avatar Thomas
Browse files

- Add "Filter this post" feature

parent e7e42806
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -44,7 +44,9 @@ import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.ActivityFiltersBinding;
import app.fedilab.android.databinding.PopupAddFilterBinding;
import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.FilterStatus;
import app.fedilab.android.mastodon.ui.drawer.FilterAdapter;
import app.fedilab.android.mastodon.ui.drawer.FilteredStatusAdapter;
import app.fedilab.android.mastodon.ui.drawer.KeywordAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.FiltersVM;

@@ -140,6 +142,29 @@ public class FilterActivity extends BaseBarActivity implements FilterAdapter.Del
        popupAddFilterBinding.lvKeywords.setNestedScrollingEnabled(false);
        popupAddFilterBinding.lvKeywords.setLayoutManager(new LinearLayoutManager(context));

        // Setup filtered statuses list
        if (filter != null && filter.statuses != null && !filter.statuses.isEmpty()) {
            popupAddFilterBinding.filteredStatusesContainer.setVisibility(View.VISIBLE);
            FilteredStatusAdapter filteredStatusAdapter = new FilteredStatusAdapter(filter.statuses, null);
            filteredStatusAdapter.setDeleteListener((filterStatus, position) -> {
                new MaterialAlertDialogBuilder(context)
                        .setTitle(R.string.delete)
                        .setMessage(filterStatus.status_id)
                        .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss())
                        .setPositiveButton(R.string.delete, (dialog, which) -> {
                            filtersVM.removeStatusFromFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterStatus.id);
                            filteredStatusAdapter.removeItem(position);
                            if (filter.statuses.isEmpty()) {
                                popupAddFilterBinding.filteredStatusesContainer.setVisibility(View.GONE);
                            }
                        })
                        .show();
            });
            popupAddFilterBinding.lvFilteredStatuses.setAdapter(filteredStatusAdapter);
            popupAddFilterBinding.lvFilteredStatuses.setNestedScrollingEnabled(false);
            popupAddFilterBinding.lvFilteredStatuses.setLayoutManager(new LinearLayoutManager(context));
        }

        popupAddFilterBinding.addKeyword.setOnClickListener(v -> {
            Filter.KeywordsParams keywordsParams = new Filter.KeywordsParams();
            keywordsParams.whole_word = true;
+23 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package app.fedilab.android.mastodon.client.endpoints;
import java.util.List;

import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.FilterStatus;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
@@ -102,5 +103,27 @@ public interface MastodonFiltersService {
            @Path("id") String id
    );

    //Get statuses for a filter
    @GET("filters/{filter_id}/statuses")
    Call<List<FilterStatus>> getStatusFilter(
            @Header("Authorization") String token,
            @Path("filter_id") String filter_id
    );

    //Add a status to a filter
    @FormUrlEncoded
    @POST("filters/{filter_id}/statuses")
    Call<FilterStatus> addStatusFilter(
            @Header("Authorization") String token,
            @Path("filter_id") String filter_id,
            @Field("status_id") String status_id
    );

    //Remove a status from a filter
    @DELETE("filters/statuses/{id}")
    Call<Void> removeStatusFilter(
            @Header("Authorization") String token,
            @Path("id") String id
    );

}
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ public class Filter implements Serializable {
    public String filter_action;
    @SerializedName("keywords")
    public List<KeywordsAttributes> keywords;
    @SerializedName("statuses")
    public List<FilterStatus> statuses;

    public static String getValueOf(FilterParams filterParams) {
        Gson gson = new Gson();
+26 −0
Original line number Diff line number Diff line
package app.fedilab.android.mastodon.client.entities.api;
/* Copyright 2026 Thomas Schneider
 *
 * This file is a part of Fedilab
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with Fedilab; if not,
 * see <http://www.gnu.org/licenses>. */

import com.google.gson.annotations.SerializedName;

import java.io.Serializable;

public class FilterStatus implements Serializable {
    @SerializedName("id")
    public String id;
    @SerializedName("status_id")
    public String status_id;
}
+47 −28
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import app.fedilab.android.mastodon.client.endpoints.MastodonFiltersService;
import app.fedilab.android.mastodon.client.entities.api.Account;
import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Filter;
import app.fedilab.android.mastodon.client.entities.api.FilterStatus;
import app.fedilab.android.mastodon.client.entities.api.Notification;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
@@ -123,12 +124,15 @@ public class TimelineHelper {
                    contextMatches = filter.context.contains("public");
                }

                if (!contextMatches || filter.keywords == null || filter.keywords.isEmpty()) {
                boolean hasKeywords = filter.keywords != null && !filter.keywords.isEmpty();
                boolean hasStatuses = filter.statuses != null && !filter.statuses.isEmpty();
                if (!contextMatches || (!hasKeywords && !hasStatuses)) {
                    continue;
                }

                // Precompile patterns for this filter
                List<Pattern> patterns = new ArrayList<>();
                if (hasKeywords) {
                    for (Filter.KeywordsAttributes filterKeyword : filter.keywords) {
                        String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : "";
                        String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : "";
@@ -140,6 +144,7 @@ public class TimelineHelper {
                        }
                        patterns.add(p);
                    }
                }
                compiledFilters.add(new CompiledFilter(filter, patterns));
            }
        }
@@ -160,6 +165,19 @@ public class TimelineHelper {
            for (CompiledFilter compiledFilter : compiledFilters) {
                boolean matched = false;

                // Check if status ID is in filter's statuses list
                if (compiledFilter.filter.statuses != null) {
                    String statusIdToCheck = status.reblog != null ? status.reblog.id : status.id;
                    for (FilterStatus filterStatus : compiledFilter.filter.statuses) {
                        if (filterStatus.status_id != null && filterStatus.status_id.equals(statusIdToCheck)) {
                            matched = true;
                            break;
                        }
                    }
                }

                // Check keywords (content, spoiler, media) if not already matched by status ID
                if (!matched) {
                    // Check content
                    for (Pattern pattern : compiledFilter.patterns) {
                        if (pattern.matcher(content).find()) {
@@ -192,6 +210,7 @@ public class TimelineHelper {
                            }
                        }
                    }
                }

                if (matched) {
                    status.filteredByApp = compiledFilter.filter;
Loading