Loading app/src/main/java/app/fedilab/android/BaseMainActivity.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 -> { Loading app/src/main/java/app/fedilab/android/activities/FilterActivity.java +62 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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); Loading @@ -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()) Loading @@ -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(); Loading @@ -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); Loading @@ -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) { Loading app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java +3 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 Loading app/src/main/java/app/fedilab/android/client/entities/api/Filter.java +2 −4 Original line number Diff line number Diff line Loading @@ -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") Loading app/src/main/java/app/fedilab/android/helper/TimelineHelper.java +108 −75 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading @@ -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(); Loading Loading @@ -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) Loading Loading @@ -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; } Loading @@ -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); } } } } } } } } Loading @@ -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; }); Loading @@ -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 Loading
app/src/main/java/app/fedilab/android/BaseMainActivity.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 -> { Loading
app/src/main/java/app/fedilab/android/activities/FilterActivity.java +62 −24 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 { Loading @@ -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); Loading Loading @@ -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) { Loading @@ -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); Loading @@ -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()) Loading @@ -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(); Loading @@ -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); Loading @@ -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) { Loading
app/src/main/java/app/fedilab/android/client/endpoints/MastodonFiltersService.java +3 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 Loading
app/src/main/java/app/fedilab/android/client/entities/api/Filter.java +2 −4 Original line number Diff line number Diff line Loading @@ -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") Loading
app/src/main/java/app/fedilab/android/helper/TimelineHelper.java +108 −75 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } Loading @@ -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(); Loading Loading @@ -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) Loading Loading @@ -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; } Loading @@ -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); } } } } } } } } Loading @@ -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; }); Loading @@ -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