Commit 74599d8a authored by Thomas's avatar Thomas
Browse files

Comment #467 #464 - Support API endpoints

parent 8bf21db6
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -147,6 +147,7 @@ import app.fedilab.android.ui.fragment.timeline.FragmentMastodonConversation;
import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline;
import app.fedilab.android.ui.fragment.timeline.FragmentNotificationContainer;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.FiltersVM;
import app.fedilab.android.viewmodel.mastodon.InstancesVM;
import app.fedilab.android.viewmodel.mastodon.TimelinesVM;
import app.fedilab.android.viewmodel.mastodon.TopBarVM;
@@ -691,7 +692,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
                            editor.apply();
                        });
                //Retrieve filters
                new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class).getFilters(currentInstance, currentToken)
                new ViewModelProvider(BaseMainActivity.this).get(FiltersVM.class).getFilters(currentInstance, currentToken)
                        .observe(BaseMainActivity.this, filters -> mainFilters = filters);
                new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class).getConnectedAccount(currentInstance, currentToken)
                        .observe(BaseMainActivity.this, mastodonAccount -> {
+62 −24
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import android.widget.Button;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
@@ -35,6 +37,7 @@ import androidx.lifecycle.ViewModelStoreOwner;
import androidx.recyclerview.widget.LinearLayoutManager;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;

@@ -42,11 +45,12 @@ import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Filter;
import app.fedilab.android.databinding.ActivityFiltersBinding;
import app.fedilab.android.databinding.KeywordsLayoutBinding;
import app.fedilab.android.databinding.PopupAddFilterBinding;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.helper.ThemeHelper;
import app.fedilab.android.ui.drawer.FilterAdapter;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.FiltersVM;

public class FilterActivity extends BaseActivity implements FilterAdapter.Delete {

@@ -64,7 +68,7 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete
    public static void addEditFilter(Context context, Filter filter, FilterAdapter.FilterAction listener) {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context, Helper.dialogStyle());
        PopupAddFilterBinding popupAddFilterBinding = PopupAddFilterBinding.inflate(LayoutInflater.from(context));
        AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class);
        FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
        dialogBuilder.setView(popupAddFilterBinding.getRoot());
        ArrayAdapter<CharSequence> adapterResize = ArrayAdapter.createFromResource(Objects.requireNonNull(context),
                R.array.filter_expire, android.R.layout.simple_spinner_dropdown_item);
@@ -103,8 +107,14 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete
            }
        });

        popupAddFilterBinding.addKeyword.setOnClickListener(v -> {
            KeywordsLayoutBinding keywordsLayoutBinding = KeywordsLayoutBinding.inflate(LayoutInflater.from(context));
            keywordsLayoutBinding.deleteKeyword.setOnClickListener(v2 -> popupAddFilterBinding.keywordsContainer.removeView(keywordsLayoutBinding.deleteKeyword));
            popupAddFilterBinding.keywordsContainer.addView(keywordsLayoutBinding.getRoot());
        });

        if (filter != null) {
            popupAddFilterBinding.addPhrase.setText(filter.phrase);
            popupAddFilterBinding.addTitle.setText(filter.title);
            if (filter.context != null)
                for (String val : filter.context) {
                    switch (val) {
@@ -125,14 +135,20 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete
                            break;
                    }
                }
            popupAddFilterBinding.contextWholeWord.setChecked(filter.whole_word);
            if (filter.irreversible) {
                popupAddFilterBinding.actionRemove.setChecked(true);
                popupAddFilterBinding.actionHide.setChecked(false);
            } else {
                popupAddFilterBinding.actionRemove.setChecked(false);
                popupAddFilterBinding.actionHide.setChecked(true);
            if (filter.keywords != null && filter.keywords.size() > 0) {
                for (Filter.FilterKeyword filterKeyword : filter.keywords) {
                    KeywordsLayoutBinding keywordsLayoutBinding = KeywordsLayoutBinding.inflate(LayoutInflater.from(context));
                    keywordsLayoutBinding.keywordPhrase.setText(filterKeyword.keyword);
                    keywordsLayoutBinding.wholeWord.setChecked(filterKeyword.whole_word);
                    keywordsLayoutBinding.deleteKeyword.setOnClickListener(v -> popupAddFilterBinding.keywordsContainer.removeView(keywordsLayoutBinding.deleteKeyword));
                    popupAddFilterBinding.keywordsContainer.addView(keywordsLayoutBinding.getRoot());
                }
            }
        } else {
            //Add at least a view
            KeywordsLayoutBinding keywordsLayoutBinding = KeywordsLayoutBinding.inflate(LayoutInflater.from(context));
            keywordsLayoutBinding.deleteKeyword.setOnClickListener(v -> popupAddFilterBinding.keywordsContainer.removeView(keywordsLayoutBinding.deleteKeyword));
            popupAddFilterBinding.keywordsContainer.addView(keywordsLayoutBinding.getRoot());
        }
        popupAddFilterBinding.actionRemove.setOnClickListener(v -> {
            popupAddFilterBinding.actionHide.setChecked(false);
@@ -149,15 +165,33 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete

            Button button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
            button.setOnClickListener(view -> {
                if (popupAddFilterBinding.addPhrase.getText() == null || popupAddFilterBinding.addPhrase.getText().toString().trim().length() == 0) {
                    popupAddFilterBinding.addPhrase.setError(context.getString(R.string.cannot_be_empty));
                    return;

                int keywordsItem = popupAddFilterBinding.keywordsContainer.getChildCount();
                List<Filter.KeywordsAttributes> keywordsAttributes = null;
                boolean canBeSent = true;

                for (int i = 0; i < keywordsItem; i++) {
                    View itemView = popupAddFilterBinding.keywordsContainer.getChildAt(i);
                    AppCompatEditText keyword = itemView.findViewById(R.id.keyword_phrase);
                    AppCompatCheckBox whole_word = itemView.findViewById(R.id.whole_word);
                    keywordsAttributes = new ArrayList<>();
                    if (keyword != null && whole_word != null) {
                        Filter.KeywordsAttributes keywordsAttr = new Filter.KeywordsAttributes();
                        keywordsAttr.keyword = keyword.getText().toString();
                        keywordsAttr.whole_word = whole_word.isChecked();
                        if (keywordsAttr.keyword.trim().isEmpty()) {
                            keyword.setError(context.getString(R.string.cannot_be_empty));
                            canBeSent = false;
                        }
                        keywordsAttributes.add(keywordsAttr);
                    }
                }

                if (!popupAddFilterBinding.contextConversation.isChecked() && !popupAddFilterBinding.contextHome.isChecked() && !popupAddFilterBinding.contextPublic.isChecked() && !popupAddFilterBinding.contextNotification.isChecked() && !popupAddFilterBinding.contextProfiles.isChecked()) {
                    popupAddFilterBinding.contextDescription.setError(context.getString(R.string.cannot_be_empty));
                    return;
                    canBeSent = false;
                }
                if (popupAddFilterBinding.addPhrase.getText() != null && popupAddFilterBinding.addPhrase.getText().toString().trim().length() > 0) {
                if (canBeSent) {
                    Filter filterSent = new Filter();
                    ArrayList<String> contextFilter = new ArrayList<>();
                    if (popupAddFilterBinding.contextHome.isChecked())
@@ -171,15 +205,19 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete
                    if (popupAddFilterBinding.contextProfiles.isChecked())
                        contextFilter.add("account");
                    filterSent.context = contextFilter;
                    filterSent.expires_at_sent = expire[0];
                    filterSent.phrase = popupAddFilterBinding.addPhrase.getText().toString();
                    filterSent.whole_word = popupAddFilterBinding.contextWholeWord.isChecked();
                    filterSent.irreversible = popupAddFilterBinding.actionRemove.isChecked();
                    if (expire[0] != -1) {
                        Calendar calendar = Calendar.getInstance();
                        calendar.add(Calendar.SECOND, expire[0]);
                        filterSent.expires_at = calendar.getTime();
                    } else {
                        filterSent.expires_at = null;
                    }
                    filterSent.filter_action = popupAddFilterBinding.actionHide.isChecked() ? "hide" : "warn";
                    if (filter != null) {
                        accountsVM.editFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filter.id, filterSent.phrase, filterSent.context, filterSent.irreversible, filterSent.whole_word, filterSent.expires_at_sent)
                        filtersVM.editFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filter.id, filterSent.title, filterSent.expires_at, filterSent.context, filterSent.filter_action, keywordsAttributes)
                                .observe((LifecycleOwner) context, listener::callback);
                    } else {
                        accountsVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterSent.phrase, filterSent.context, filterSent.irreversible, filterSent.whole_word, filterSent.expires_at_sent)
                        filtersVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterSent.title, filterSent.expires_at, filterSent.context, filterSent.filter_action, keywordsAttributes)
                                .observe((LifecycleOwner) context, listener::callback);
                    }
                    alertDialog.dismiss();
@@ -192,7 +230,7 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete
        alertDialog.setOnDismissListener(dialogInterface -> {
            //Hide keyboard
            InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(popupAddFilterBinding.addPhrase.getWindowToken(), 0);
            imm.hideSoftInputFromWindow(popupAddFilterBinding.addTitle.getWindowToken(), 0);
        });
        if (alertDialog.getWindow() != null) {
            alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
@@ -213,8 +251,8 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete
            getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary)));
        }

        AccountsVM accountsVM = new ViewModelProvider(FilterActivity.this).get(AccountsVM.class);
        accountsVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
        FiltersVM filtersVM = new ViewModelProvider(FilterActivity.this).get(FiltersVM.class);
        filtersVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken)
                .observe(FilterActivity.this, filters -> {
                    BaseMainActivity.mainFilters = filters;
                    if (filters != null && filters.size() > 0) {
+3 −2
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ package app.fedilab.android.client.endpoints;
 * see <http://www.gnu.org/licenses>. */


import java.util.Date;
import java.util.List;

import app.fedilab.android.client.entities.api.Filter;
@@ -49,7 +50,7 @@ public interface MastodonFiltersService {
    Call<Filter> addFilter(
            @Header("Authorization") String token,
            @Field("title") String title,
            @Field("expires_in") Integer expires_in,
            @Field("expires_at") Date expires_at,
            @Field("filter_action") String filter_action,
            @Field("context[]") List<String> context,
            @Field("keywords_attributes") List<Filter.KeywordsAttributes> keywordsAttributes
@@ -62,7 +63,7 @@ public interface MastodonFiltersService {
            @Header("Authorization") String token,
            @Path("id") String id,
            @Field("title") String title,
            @Field("expires_in") Integer expires_in,
            @Field("expires_at") Date expires_at,
            @Field("filter_action") String filter_action,
            @Field("context[]") List<String> context,
            @Field("keywords_attributes") List<Filter.KeywordsAttributes> keywordsAttributes
+2 −4
Original line number Diff line number Diff line
@@ -24,12 +24,10 @@ import java.util.List;
public class Filter implements Serializable {
    @SerializedName("id")
    public String id;
    @SerializedName("phrase")
    public String phrase;
    @SerializedName("title")
    public String title;
    @SerializedName("context")
    public List<String> context;
    @SerializedName("whole_word")
    public boolean whole_word;
    @SerializedName("expires_at")
    public Date expires_at;
    @SerializedName("filter_action")
+108 −75
Original line number Diff line number Diff line
@@ -33,12 +33,12 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;

import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.client.endpoints.MastodonAccountsService;
import app.fedilab.android.client.endpoints.MastodonFiltersService;
import app.fedilab.android.client.entities.api.Filter;
import app.fedilab.android.client.entities.api.Notification;
import app.fedilab.android.client.entities.api.Status;
import app.fedilab.android.client.entities.app.Timeline;
import app.fedilab.android.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.viewmodel.mastodon.FiltersVM;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Response;
@@ -47,18 +47,18 @@ import retrofit2.converter.gson.GsonConverterFactory;

public class TimelineHelper {

    private static MastodonAccountsService init(Context context) {
    private static MastodonFiltersService initv2(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://" + BaseMainActivity.currentInstance + "/api/v1/")
                .baseUrl("https://" + BaseMainActivity.currentInstance + "/api/v2/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();
        return retrofit.create(MastodonAccountsService.class);
        return retrofit.create(MastodonFiltersService.class);
    }


@@ -74,9 +74,9 @@ public class TimelineHelper {
        //A security to make sure filters have been fetched before displaying messages
        List<Status> statusesToRemove = new ArrayList<>();
        if (!BaseMainActivity.filterFetched) {
            MastodonAccountsService mastodonAccountsService = init(context);
            MastodonFiltersService mastodonFiltersService = initv2(context);
            List<Filter> filterList;
            Call<List<Filter>> getFiltersCall = mastodonAccountsService.getFilters(BaseMainActivity.currentToken);
            Call<List<Filter>> getFiltersCall = mastodonFiltersService.getFilters(BaseMainActivity.currentToken);
            if (getFiltersCall != null) {
                try {
                    Response<List<Filter>> getFiltersResponse = getFiltersCall.execute();
@@ -111,9 +111,10 @@ public class TimelineHelper {
                } else {
                    if (!filter.context.contains("public")) continue;
                }

                if (filter.whole_word) {
                    Pattern p = Pattern.compile("(^|\\W)(" + Pattern.quote(filter.phrase) + ")($|\\W)", Pattern.CASE_INSENSITIVE);
                if (filter.keywords != null && filter.keywords.size() > 0) {
                    for (Filter.FilterKeyword filterKeyword : filter.keywords) {
                        if (filterKeyword.whole_word) {
                            Pattern p = Pattern.compile("(^|\\W)(" + Pattern.quote(filterKeyword.keyword) + ")($|\\W)", Pattern.CASE_INSENSITIVE);
                            for (Status status : statuses) {
                                String content;
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
@@ -144,7 +145,7 @@ public class TimelineHelper {
                                    content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content, Html.FROM_HTML_MODE_LEGACY).toString();
                                else
                                    content = Html.fromHtml(status.reblog != null ? status.reblog.content : status.content).toString();
                        if (content.contains(filter.phrase)) {
                                if (content.contains(filterKeyword.keyword)) {
                                    statusesToRemove.add(status);
                                    continue;
                                }
@@ -155,12 +156,14 @@ public class TimelineHelper {
                                        spoilerText = Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
                                    else
                                        spoilerText = Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text).toString();
                            if (spoilerText.contains(filter.phrase)) {
                                    if (spoilerText.contains(filterKeyword.keyword)) {
                                        statusesToRemove.add(status);
                                    }
                                }
                            }
                        }
                    }
                }

            }
        }
@@ -182,8 +185,8 @@ public class TimelineHelper {
        List<Notification> notificationToRemove = 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 -> {
                FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
                filtersVM.getFilters(BaseMainActivity.currentInstance, BaseMainActivity.currentToken).observe((LifecycleOwner) context, filters -> {
                    BaseMainActivity.filterFetched = true;
                    BaseMainActivity.mainFilters = filters;
                });
@@ -192,51 +195,81 @@ public class TimelineHelper {
            }
        }
        //If there are filters:
        if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0) {
        if (BaseMainActivity.mainFilters != null && BaseMainActivity.mainFilters.size() > 0 && notifications != null && notifications.size() > 0) {

            //Loop through filters
            for (Filter filter : BaseMainActivity.mainFilters) {
                if (filter.irreversible) { //Dealt by the server
                if (filter.expires_at != null && filter.expires_at.before(new Date())) {
                    //Expired filter
                    continue;
                }
                for (String filterContext : filter.context) {
                    if (Timeline.TimeLineEnum.NOTIFICATION.getValue().equalsIgnoreCase(filterContext)) {
                        if (filter.whole_word) {
                            Pattern p = Pattern.compile("(^" + Pattern.quote(filter.phrase) + "\\b|\\b" + Pattern.quote(filter.phrase) + "$)", Pattern.CASE_INSENSITIVE);

                if (!filter.context.contains("notification")) continue;
                if (filter.keywords != null && filter.keywords.size() > 0) {
                    for (Filter.FilterKeyword filterKeyword : filter.keywords) {
                        if (filterKeyword.whole_word) {
                            Pattern p = Pattern.compile("(^|\\W)(" + Pattern.quote(filterKeyword.keyword) + ")($|\\W)", Pattern.CASE_INSENSITIVE);
                            for (Notification notification : notifications) {
                                notification.cached = cached;
                                if (notification.status != null) {
                                if (notification.status == null) {
                                    continue;
                                }
                                String content;
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                                        content = Html.fromHtml(notification.status.content, Html.FROM_HTML_MODE_LEGACY).toString();
                                    content = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.content : notification.status.content, Html.FROM_HTML_MODE_LEGACY).toString();
                                else
                                        content = Html.fromHtml(notification.status.content).toString();
                                    content = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.content : notification.status.content).toString();
                                Matcher m = p.matcher(content);
                                if (m.find()) {
                                    notificationToRemove.add(notification);
                                    continue;
                                }
                                if (notification.status.spoiler_text != null) {
                                    String spoilerText;
                                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                                        spoilerText = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.spoiler_text : notification.status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
                                    else
                                        spoilerText = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.spoiler_text : notification.status.spoiler_text).toString();
                                    Matcher ms = p.matcher(spoilerText);
                                    if (ms.find()) {
                                        notificationToRemove.add(notification);
                                    }
                                }
                            }
                        } else {
                            for (Notification notification : notifications) {
                                if (notification.status == null) {
                                    continue;
                                }
                                String content;
                                notification.cached = cached;
                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                                    content = Html.fromHtml(notification.status.content, Html.FROM_HTML_MODE_LEGACY).toString();
                                    content = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.content : notification.status.content, Html.FROM_HTML_MODE_LEGACY).toString();
                                else
                                    content = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.content : notification.status.content).toString();
                                if (content.contains(filterKeyword.keyword)) {
                                    notificationToRemove.add(notification);
                                    continue;
                                }

                                if (notification.status.spoiler_text != null) {
                                    String spoilerText;
                                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
                                        spoilerText = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.spoiler_text : notification.status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString();
                                    else
                                    content = Html.fromHtml(notification.status.content).toString();
                                if (content.contains(filter.phrase)) {
                                        spoilerText = Html.fromHtml(notification.status.reblog != null ? notification.status.reblog.spoiler_text : notification.status.spoiler_text).toString();
                                    if (spoilerText.contains(filterKeyword.keyword)) {
                                        notificationToRemove.add(notification);
                                    }
                                }
                            }
                    } else {
                        for (Notification notification : notifications) {
                            notification.cached = cached;
                        }
                    }
                }

            }
        }
        if (notifications != null) {
            notifications.removeAll(notificationToRemove);
        }
        return notifications;
    }

Loading