diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 21a9cb19a..4682d6318 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -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 -> { diff --git a/app/src/main/java/app/fedilab/android/activities/FilterActivity.java b/app/src/main/java/app/fedilab/android/activities/FilterActivity.java index 73a851e82..d2f78cb74 100644 --- a/app/src/main/java/app/fedilab/android/activities/FilterActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/FilterActivity.java @@ -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 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 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 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) { diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java index 201080d99..eea845921 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java @@ -15,6 +15,7 @@ package app.fedilab.android.client.endpoints; * see . */ +import java.util.Date; import java.util.List; import app.fedilab.android.client.entities.api.Filter; @@ -49,7 +50,7 @@ public interface MastodonFiltersService { Call 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 context, @Field("keywords_attributes") List 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 context, @Field("keywords_attributes") List keywordsAttributes diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Filter.java b/app/src/main/java/app/fedilab/android/client/entities/api/Filter.java index 6c68896c0..35e266d63 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Filter.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Filter.java @@ -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 context; - @SerializedName("whole_word") - public boolean whole_word; @SerializedName("expires_at") public Date expires_at; @SerializedName("filter_action") 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 06e480147..57f7fe5c3 100644 --- a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java @@ -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 statusesToRemove = new ArrayList<>(); if (!BaseMainActivity.filterFetched) { - MastodonAccountsService mastodonAccountsService = init(context); + MastodonFiltersService mastodonFiltersService = initv2(context); List filterList; - Call> getFiltersCall = mastodonAccountsService.getFilters(BaseMainActivity.currentToken); + Call> getFiltersCall = mastodonFiltersService.getFilters(BaseMainActivity.currentToken); if (getFiltersCall != null) { try { Response> getFiltersResponse = getFiltersCall.execute(); @@ -111,52 +111,55 @@ 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); - for (Status status : statuses) { - String content; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - 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(); - Matcher m = p.matcher(content); - if (m.find()) { - statusesToRemove.add(status); - continue; - } - if (status.spoiler_text != null) { - String spoilerText; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - 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(); - Matcher ms = p.matcher(spoilerText); - if (ms.find()) { - statusesToRemove.add(status); + 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) + 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(); + Matcher m = p.matcher(content); + if (m.find()) { + statusesToRemove.add(status); + continue; + } + if (status.spoiler_text != null) { + String spoilerText; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + 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(); + Matcher ms = p.matcher(spoilerText); + if (ms.find()) { + statusesToRemove.add(status); + } + } } - } - } - } else { - for (Status status : statuses) { - String content; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - 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)) { - statusesToRemove.add(status); - continue; - } + } else { + for (Status status : statuses) { + String content; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + 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(filterKeyword.keyword)) { + statusesToRemove.add(status); + continue; + } - if (status.spoiler_text != null) { - String spoilerText; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - 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)) { - statusesToRemove.add(status); + if (status.spoiler_text != null) { + String spoilerText; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + 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(filterKeyword.keyword)) { + statusesToRemove.add(status); + } + } } } } @@ -182,8 +185,8 @@ public class TimelineHelper { List 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) { - String content; + if (notification.status == null) { + continue; + } + String content; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + 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(); + 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) - content = Html.fromHtml(notification.status.content, Html.FROM_HTML_MODE_LEGACY).toString(); + 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(); - Matcher m = p.matcher(content); - if (m.find()) { + 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.content).toString(); - if (content.contains(filter.phrase)) { + 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 + 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; - } } } + } } - notifications.removeAll(notificationToRemove); + if (notifications != null) { + notifications.removeAll(notificationToRemove); + } return notifications; } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java index 8c6e62a9a..e7f8bf287 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java @@ -33,7 +33,7 @@ import app.fedilab.android.activities.FilterActivity; import app.fedilab.android.client.entities.api.Filter; import app.fedilab.android.databinding.DrawerFilterBinding; import app.fedilab.android.helper.Helper; -import app.fedilab.android.viewmodel.mastodon.AccountsVM; +import app.fedilab.android.viewmodel.mastodon.FiltersVM; public class FilterAdapter extends RecyclerView.Adapter { @@ -69,8 +69,8 @@ public class FilterAdapter extends RecyclerView.Adapter FilterActivity.addEditFilter(context, filter, filter1 -> { if (filter1 != null) { - BaseMainActivity.mainFilters.get(position).phrase = filter1.phrase; BaseMainActivity.mainFilters.get(position).context = filter1.context; - BaseMainActivity.mainFilters.get(position).whole_word = filter1.whole_word; - BaseMainActivity.mainFilters.get(position).irreversible = filter1.irreversible; BaseMainActivity.mainFilters.get(position).expires_at = filter1.expires_at; } filterAdapter.notifyItemChanged(position); @@ -93,8 +90,8 @@ public class FilterAdapter extends RecyclerView.Adapter { - AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class); - accountsVM.removeFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filter.id); + FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class); + filtersVM.removeFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filter.id); filters.remove(filter); if (filters.size() == 0) { delete.allFiltersDeleted(); diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java index 883866ee8..e93eb5adf 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/FiltersVM.java @@ -25,6 +25,7 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; @@ -129,24 +130,15 @@ public class FiltersVM extends AndroidViewModel { /** * Create a filter * - * @param phrase Text to be filtered - * @param filterContext Array of enumerable strings "home", "notifications", "public", "thread". At least one context must be specified. - * @param irreversible Should the server irreversibly drop matching entities from home and notifications? - * @param wholeWord Consider word boundaries? - * @param expiresIn Number of seconds from now the filter should expire. Otherwise, null for a filter that doesn't expire. * @return {@link LiveData} containing a {@link Filter} */ public LiveData addFilter(@NonNull String instance, String token, - @NonNull String phrase, - @NonNull List filterContext, - boolean irreversible, - boolean wholeWord, - int expiresIn) { + @NonNull String title, Date expires_at, @NonNull List filterContext, String filter_action, List keywordsAttributes) { filterMutableLiveData = new MutableLiveData<>(); MastodonFiltersService mastodonFiltersService = initV2(instance); new Thread(() -> { Filter filter = null; - Call addFilterCall = mastodonFiltersService.addFilter(token, phrase, filterContext, irreversible, wholeWord, expiresIn == -1 ? null : expiresIn); + Call addFilterCall = mastodonFiltersService.addFilter(token, title, expires_at, filter_action, filterContext, keywordsAttributes); if (addFilterCall != null) { try { Response addFiltersResponse = addFilterCall.execute(); @@ -168,20 +160,14 @@ public class FiltersVM extends AndroidViewModel { /** * Update a filter * - * @param id ID of the filter - * @param phrase Text to be filtered - * @param filterContext Array of enumerable strings "home", "notifications", "public", "thread". At least one context must be specified. - * @param irreversible Should the server irreversibly drop matching entities from home and notifications? - * @param wholeWord Consider word boundaries? - * @param expiresIn Number of seconds from now the filter should expire. Otherwise, null for a filter that doesn't expire. * @return {@link LiveData} containing a {@link Filter} */ - public LiveData editFilter(@NonNull String instance, String token, @NonNull String id, @NonNull String phrase, @NonNull List filterContext, boolean irreversible, boolean wholeWord, int expiresIn) { + public LiveData editFilter(@NonNull String instance, String token, @NonNull String id, @NonNull String title, Date expires_at, @NonNull List filterContext, String filter_action, List keywordsAttributes) { filterMutableLiveData = new MutableLiveData<>(); MastodonFiltersService mastodonFiltersService = initV2(instance); new Thread(() -> { Filter filter = null; - Call editFilterCall = mastodonFiltersService.editFilter(token, id, phrase, filterContext, irreversible, wholeWord, expiresIn == -1 ? null : expiresIn); + Call editFilterCall = mastodonFiltersService.editFilter(token, id, title, expires_at, filter_action, filterContext, keywordsAttributes); if (editFilterCall != null) { Log.v(Helper.TAG, "request: " + editFilterCall.request()); try { diff --git a/app/src/main/res/layout/keywords_layout.xml b/app/src/main/res/layout/keywords_layout.xml new file mode 100644 index 000000000..9252508f5 --- /dev/null +++ b/app/src/main/res/layout/keywords_layout.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_add_filter.xml b/app/src/main/res/layout/popup_add_filter.xml index d0c7939f8..e23769afe 100644 --- a/app/src/main/res/layout/popup_add_filter.xml +++ b/app/src/main/res/layout/popup_add_filter.xml @@ -35,6 +35,24 @@ android:inputType="text" android:singleLine="true" /> + + + + + + + + - - - - - + android:orientation="vertical"> - + + + + + + + + + - - + android:layout_gravity="end" + android:layout_marginTop="10dp" + android:text="@string/add_keyword" + android:textColor="@color/cyanea_accent_dark_reference" + app:icon="@drawable/ic_baseline_add_24" + app:iconTint="@color/cyanea_primary_dark_reference" + app:strokeColor="@color/cyanea_accent_dark_reference" /> + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d7b4c13d9..286166b92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1997,4 +1997,7 @@ Completely hide the filtered content, behaving as if it did not exist Home and lists Title + Keyword or phrase + Delete keyword + Add keyword