Loading app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java +24 −13 Original line number Diff line number Diff line Loading @@ -966,30 +966,41 @@ public class Helper { @Nullable Bundle args, @Nullable String tag, @Nullable String backStackName) { // Check if FragmentManager is in a valid state if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) { return fragment; } Fragment _fragment = fragmentManager.findFragmentByTag(tag); FragmentTransaction ft = fragmentManager.beginTransaction(); ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit); Fragment _fragment = fragmentManager.findFragmentByTag(tag); if (_fragment != null && _fragment.isAdded()) { ft.show(_fragment).commitAllowingStateLoss(); fragment = _fragment; // Reuse existing fragment ft.show(_fragment); try { ft.commit(); } catch (IllegalStateException e) { // State already saved, cannot commit return _fragment; } return _fragment; } else { // Add new fragment if (args != null) fragment.setArguments(args); ft = fragmentManager.beginTransaction(); ft.add(containerViewId, fragment, tag); if (backStackName != null) { try { ft.addToBackStack(backStackName); }catch (Exception ignored){} } if (!fragmentManager.isDestroyed()) { ft.commitAllowingStateLoss(); } } if(!fragmentManager.isDestroyed()) { fragmentManager.executePendingTransactions(); try { ft.commit(); } catch (IllegalStateException e) { // State already saved, cannot commit return fragment; } return fragment; } } /** * Load a media into a view Loading app/src/main/java/app/fedilab/android/mastodon/helper/TimelineHelper.java +188 −101 Original line number Diff line number Diff line Loading @@ -75,7 +75,7 @@ public class TimelineHelper { * @return filtered List<Status> */ public static List<Status> filterStatus(Context context, List<Status> statuses, Timeline.TimeLineEnum filterTimeLineType) { //A security to make sure filters have been fetched before displaying messages // Ensure filters have been fetched before displaying messages if (!BaseMainActivity.filterFetched && BaseMainActivity.filterFetchedRetry < 3) { MastodonFiltersService mastodonFiltersService = initv2(context); List<Filter> filterList; Loading @@ -95,27 +95,40 @@ public class TimelineHelper { BaseMainActivity.filterFetchedRetry++; } //If there are filters: if (BaseMainActivity.mainFilters != null && !BaseMainActivity.mainFilters.isEmpty() && statuses != null && !statuses.isEmpty()) { if (statuses == null || statuses.isEmpty()) { return statuses; } //Loop through filters // Precompile patterns for all active filters List<CompiledFilter> compiledFilters = new ArrayList<>(); if (BaseMainActivity.mainFilters != null && !BaseMainActivity.mainFilters.isEmpty()) { Date now = new Date(); for (Filter filter : BaseMainActivity.mainFilters) { if (filter.expires_at != null && filter.expires_at.before(new Date())) { //Expired filter // Skip expired filters if (filter.expires_at != null && filter.expires_at.before(now)) { continue; } // Check context boolean contextMatches = false; if (filterTimeLineType == Timeline.TimeLineEnum.HOME || filterTimeLineType == Timeline.TimeLineEnum.LIST) { if (!filter.context.contains("home")) continue; contextMatches = filter.context.contains("home"); } else if (filterTimeLineType == Timeline.TimeLineEnum.NOTIFICATION) { if (!filter.context.contains("notifications")) continue; contextMatches = filter.context.contains("notifications"); } else if (filterTimeLineType == Timeline.TimeLineEnum.CONTEXT) { if (!filter.context.contains("thread")) continue; contextMatches = filter.context.contains("thread"); } else if (filterTimeLineType == Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { if (!filter.context.contains("account")) continue; contextMatches = filter.context.contains("account"); } else { if (!filter.context.contains("public")) continue; contextMatches = filter.context.contains("public"); } if (filter.keywords != null && !filter.keywords.isEmpty()) { if (!contextMatches || filter.keywords == null || filter.keywords.isEmpty()) { continue; } // Precompile patterns for this filter List<Pattern> patterns = new ArrayList<>(); for (Filter.KeywordsAttributes filterKeyword : filter.keywords) { String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : ""; String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : ""; Loading @@ -125,103 +138,177 @@ public class TimelineHelper { } else { p = Pattern.compile("(" + Pattern.quote(filterKeyword.keyword) + ")", Pattern.CASE_INSENSITIVE); } patterns.add(p); } compiledFilters.add(new CompiledFilter(filter, patterns)); } } // Apply filters to statuses (inverted loop order for early exit) for (Status status : statuses) { // Skip user's own statuses if (status.account.id.equals(MainActivity.currentUserID)) { continue; } String content; try { 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(status.reblog == null && status.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) content += Html.fromHtml(status.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); else content += Html.fromHtml(status.getQuote().content).toString(); } else if(status.reblog != null && status.reblog.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) content += Html.fromHtml(status.reblog.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); else content += Html.fromHtml(status.reblog.getQuote().content).toString(); // Cache parsed HTML content String content = parseStatusContent(status); String spoilerText = parseStatusSpoiler(status); List<Attachment> mediaAttachments = status.reblog != null ? status.reblog.media_attachments : status.media_attachments; // Check against all compiled filters for (CompiledFilter compiledFilter : compiledFilters) { boolean matched = false; // Check content for (Pattern pattern : compiledFilter.patterns) { if (pattern.matcher(content).find()) { matched = true; break; } } catch (Exception e) { content = status.reblog != null ? status.reblog.content : status.content; } Matcher m = p.matcher(content); if (m.find()) { status.filteredByApp = filter; continue; // Check spoiler text if (!matched && spoilerText != null) { for (Pattern pattern : compiledFilter.patterns) { if (pattern.matcher(spoilerText).find()) { matched = true; break; } 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()) { status.filteredByApp = filter; continue; } } List<Attachment> mediaAttachments = status.reblog != null ? status.reblog.media_attachments : status.media_attachments; if(mediaAttachments != null && !mediaAttachments.isEmpty()) { // Check media attachments if (!matched && mediaAttachments != null && !mediaAttachments.isEmpty()) { for (Attachment attachment : mediaAttachments) { if (attachment.description != null) { Matcher ms = p.matcher(attachment.description ); if (ms.find()) { status.filteredByApp = filter; for (Pattern pattern : compiledFilter.patterns) { if (pattern.matcher(attachment.description).find()) { matched = true; break; } } if (matched) break; } } } if (matched) { status.filteredByApp = compiledFilter.filter; break; // Stop checking other filters for this status } } } } if (statuses != null && !statuses.isEmpty()) { // Apply additional filters for HOME timeline if (filterTimeLineType == Timeline.TimeLineEnum.HOME) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); boolean groupReblogs = sharedpreferences.getBoolean(context.getString(R.string.SET_GROUP_REBLOGS), true); if (filterTimeLineType == Timeline.TimeLineEnum.HOME) { // Index seen reblog IDs for O(1) lookup java.util.HashSet<String> seenReblogIds = new java.util.HashSet<>(); for (int i = 0; i < statuses.size(); i++) { Status currentStatus = statuses.get(i); // Check filtered accounts if (filteredAccounts != null && !filteredAccounts.isEmpty()) { for (Account account : filteredAccounts) { if (account.acct.equals(statuses.get(i).account.acct) || (statuses.get(i).reblog != null && account.acct.equals(statuses.get(i).reblog.account.acct))) { if (account.acct.equals(currentStatus.account.acct) || (currentStatus.reblog != null && account.acct.equals(currentStatus.reblog.account.acct))) { Filter filterCustom = new Filter(); filterCustom.filter_action = "hide"; ArrayList<String> contextCustom = new ArrayList<>(); contextCustom.add("home"); filterCustom.title = "Fedilab"; filterCustom.context = contextCustom; statuses.get(i).filteredByApp = filterCustom; currentStatus.filteredByApp = filterCustom; break; } } } //Group boosts if (groupReblogs && statuses.get(i).filteredByApp == null && statuses.get(i).reblog != null) { for (int j = 0; j < i; j++) { if (statuses.get(j).reblog != null && statuses.get(j).reblog.id.equals(statuses.get(i).reblog.id)) { // Group duplicate reblogs using HashSet if (groupReblogs && currentStatus.filteredByApp == null && currentStatus.reblog != null) { if (seenReblogIds.contains(currentStatus.reblog.id)) { Filter filterCustom = new Filter(); filterCustom.filter_action = "hide"; ArrayList<String> contextCustom = new ArrayList<>(); contextCustom.add("home"); filterCustom.title = "Fedilab reblog"; filterCustom.context = contextCustom; statuses.get(i).filteredByApp = filterCustom; currentStatus.filteredByApp = filterCustom; } else { seenReblogIds.add(currentStatus.reblog.id); } } } } return statuses; } /** * Parse HTML content from status (cached per status) */ private static String parseStatusContent(Status status) { try { 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(); } // Append quote content if present if (status.reblog == null && status.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { content += Html.fromHtml(status.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); } else { content += Html.fromHtml(status.getQuote().content).toString(); } } else if (status.reblog != null && status.reblog.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { content += Html.fromHtml(status.reblog.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); } else { content += Html.fromHtml(status.reblog.getQuote().content).toString(); } } return content; } catch (Exception e) { return status.reblog != null ? status.reblog.content : status.content; } } /** * Parse HTML spoiler text from status */ private static String parseStatusSpoiler(Status status) { if (status.spoiler_text == null) { return null; } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString(); } else { return Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text).toString(); } } catch (Exception e) { return status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text; } } /** * Helper class to hold precompiled filter patterns */ private static class CompiledFilter { final Filter filter; final List<Pattern> patterns; CompiledFilter(Filter filter, List<Pattern> patterns) { this.filter = filter; this.patterns = patterns; } return statuses; } /** Loading app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +31 −8 Original line number Diff line number Diff line Loading @@ -1746,9 +1746,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> } else { measuredWidth = holder.binding.media.mediaContainer.getWidth(); } if (adapter != null && statusList != null) { adapter.notifyItemChanged(0, statusList.size()); } } }); } Loading Loading @@ -1939,6 +1936,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> player.setMediaSource(videoSource); player.prepare(); player.setPlayWhenReady(true); holder.activePlayers.add(player); } catch (Exception e) { e.printStackTrace(); } Loading @@ -1951,7 +1949,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> adapter.notifyItemChanged(position); if (timeout > 0) { new CountDownTimer((timeout * 1000L), 1000) { if (holder.activeTimer != null) { holder.activeTimer.cancel(); } holder.activeTimer = new CountDownTimer((timeout * 1000L), 1000) { public void onTick(long millisUntilFinished) { } Loading @@ -1959,7 +1960,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> statusToDeal.sensitive = true; adapter.notifyItemChanged(position); } }.start(); }; holder.activeTimer.start(); } return; } Loading Loading @@ -2032,6 +2034,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> player.setMediaSource(videoSource); player.prepare(); player.setPlayWhenReady(true); holder.activePlayers.add(player); } catch (Exception e) { e.printStackTrace(); } Loading @@ -2043,7 +2046,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> adapter.notifyItemChanged(position); if (timeout > 0) { new CountDownTimer((timeout * 1000L), 1000) { if (holder.activeTimer != null) { holder.activeTimer.cancel(); } holder.activeTimer = new CountDownTimer((timeout * 1000L), 1000) { public void onTick(long millisUntilFinished) { } Loading @@ -2051,7 +2057,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> statusToDeal.sensitive = true; adapter.notifyItemChanged(position); } }.start(); }; holder.activeTimer.start(); } return; } Loading Loading @@ -3910,7 +3917,21 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> public void onViewRecycled(@NonNull RecyclerView.ViewHolder viewHolder) { super.onViewRecycled(viewHolder); if (viewHolder instanceof StatusViewHolder holder) { //Release players //Cancel active timer if (holder.activeTimer != null) { holder.activeTimer.cancel(); holder.activeTimer = null; } //Release all tracked players for (ExoPlayer player : holder.activePlayers) { if (player != null) { player.release(); } } holder.activePlayers.clear(); //Release players in views (legacy cleanup) if (holder.binding != null) { //Cropped views if(holder.binding.media.getRoot().getChildCount() > 0) { for(int i = 0 ; i < holder.binding.media.getRoot().getChildCount() ; i++ ) { Loading Loading @@ -3950,6 +3971,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> DrawerStatusPixelfedBinding bindingPixelfed; DrawerStatusFilteredBinding bindingFiltered; DrawerStatusFilteredHideBinding bindingFilteredHide; List<ExoPlayer> activePlayers = new ArrayList<>(); CountDownTimer activeTimer; StatusViewHolder(DrawerStatusBinding itemView) { super(itemView.getRoot()); Loading Loading
app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java +24 −13 Original line number Diff line number Diff line Loading @@ -966,30 +966,41 @@ public class Helper { @Nullable Bundle args, @Nullable String tag, @Nullable String backStackName) { // Check if FragmentManager is in a valid state if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) { return fragment; } Fragment _fragment = fragmentManager.findFragmentByTag(tag); FragmentTransaction ft = fragmentManager.beginTransaction(); ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit); Fragment _fragment = fragmentManager.findFragmentByTag(tag); if (_fragment != null && _fragment.isAdded()) { ft.show(_fragment).commitAllowingStateLoss(); fragment = _fragment; // Reuse existing fragment ft.show(_fragment); try { ft.commit(); } catch (IllegalStateException e) { // State already saved, cannot commit return _fragment; } return _fragment; } else { // Add new fragment if (args != null) fragment.setArguments(args); ft = fragmentManager.beginTransaction(); ft.add(containerViewId, fragment, tag); if (backStackName != null) { try { ft.addToBackStack(backStackName); }catch (Exception ignored){} } if (!fragmentManager.isDestroyed()) { ft.commitAllowingStateLoss(); } } if(!fragmentManager.isDestroyed()) { fragmentManager.executePendingTransactions(); try { ft.commit(); } catch (IllegalStateException e) { // State already saved, cannot commit return fragment; } return fragment; } } /** * Load a media into a view Loading
app/src/main/java/app/fedilab/android/mastodon/helper/TimelineHelper.java +188 −101 Original line number Diff line number Diff line Loading @@ -75,7 +75,7 @@ public class TimelineHelper { * @return filtered List<Status> */ public static List<Status> filterStatus(Context context, List<Status> statuses, Timeline.TimeLineEnum filterTimeLineType) { //A security to make sure filters have been fetched before displaying messages // Ensure filters have been fetched before displaying messages if (!BaseMainActivity.filterFetched && BaseMainActivity.filterFetchedRetry < 3) { MastodonFiltersService mastodonFiltersService = initv2(context); List<Filter> filterList; Loading @@ -95,27 +95,40 @@ public class TimelineHelper { BaseMainActivity.filterFetchedRetry++; } //If there are filters: if (BaseMainActivity.mainFilters != null && !BaseMainActivity.mainFilters.isEmpty() && statuses != null && !statuses.isEmpty()) { if (statuses == null || statuses.isEmpty()) { return statuses; } //Loop through filters // Precompile patterns for all active filters List<CompiledFilter> compiledFilters = new ArrayList<>(); if (BaseMainActivity.mainFilters != null && !BaseMainActivity.mainFilters.isEmpty()) { Date now = new Date(); for (Filter filter : BaseMainActivity.mainFilters) { if (filter.expires_at != null && filter.expires_at.before(new Date())) { //Expired filter // Skip expired filters if (filter.expires_at != null && filter.expires_at.before(now)) { continue; } // Check context boolean contextMatches = false; if (filterTimeLineType == Timeline.TimeLineEnum.HOME || filterTimeLineType == Timeline.TimeLineEnum.LIST) { if (!filter.context.contains("home")) continue; contextMatches = filter.context.contains("home"); } else if (filterTimeLineType == Timeline.TimeLineEnum.NOTIFICATION) { if (!filter.context.contains("notifications")) continue; contextMatches = filter.context.contains("notifications"); } else if (filterTimeLineType == Timeline.TimeLineEnum.CONTEXT) { if (!filter.context.contains("thread")) continue; contextMatches = filter.context.contains("thread"); } else if (filterTimeLineType == Timeline.TimeLineEnum.ACCOUNT_TIMELINE) { if (!filter.context.contains("account")) continue; contextMatches = filter.context.contains("account"); } else { if (!filter.context.contains("public")) continue; contextMatches = filter.context.contains("public"); } if (filter.keywords != null && !filter.keywords.isEmpty()) { if (!contextMatches || filter.keywords == null || filter.keywords.isEmpty()) { continue; } // Precompile patterns for this filter List<Pattern> patterns = new ArrayList<>(); for (Filter.KeywordsAttributes filterKeyword : filter.keywords) { String sb = Pattern.compile("\\A[A-Za-z0-9_]").matcher(filterKeyword.keyword).find() ? "\\b" : ""; String eb = Pattern.compile("[A-Za-z0-9_]\\z").matcher(filterKeyword.keyword).find() ? "\\b" : ""; Loading @@ -125,103 +138,177 @@ public class TimelineHelper { } else { p = Pattern.compile("(" + Pattern.quote(filterKeyword.keyword) + ")", Pattern.CASE_INSENSITIVE); } patterns.add(p); } compiledFilters.add(new CompiledFilter(filter, patterns)); } } // Apply filters to statuses (inverted loop order for early exit) for (Status status : statuses) { // Skip user's own statuses if (status.account.id.equals(MainActivity.currentUserID)) { continue; } String content; try { 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(status.reblog == null && status.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) content += Html.fromHtml(status.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); else content += Html.fromHtml(status.getQuote().content).toString(); } else if(status.reblog != null && status.reblog.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) content += Html.fromHtml(status.reblog.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); else content += Html.fromHtml(status.reblog.getQuote().content).toString(); // Cache parsed HTML content String content = parseStatusContent(status); String spoilerText = parseStatusSpoiler(status); List<Attachment> mediaAttachments = status.reblog != null ? status.reblog.media_attachments : status.media_attachments; // Check against all compiled filters for (CompiledFilter compiledFilter : compiledFilters) { boolean matched = false; // Check content for (Pattern pattern : compiledFilter.patterns) { if (pattern.matcher(content).find()) { matched = true; break; } } catch (Exception e) { content = status.reblog != null ? status.reblog.content : status.content; } Matcher m = p.matcher(content); if (m.find()) { status.filteredByApp = filter; continue; // Check spoiler text if (!matched && spoilerText != null) { for (Pattern pattern : compiledFilter.patterns) { if (pattern.matcher(spoilerText).find()) { matched = true; break; } 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()) { status.filteredByApp = filter; continue; } } List<Attachment> mediaAttachments = status.reblog != null ? status.reblog.media_attachments : status.media_attachments; if(mediaAttachments != null && !mediaAttachments.isEmpty()) { // Check media attachments if (!matched && mediaAttachments != null && !mediaAttachments.isEmpty()) { for (Attachment attachment : mediaAttachments) { if (attachment.description != null) { Matcher ms = p.matcher(attachment.description ); if (ms.find()) { status.filteredByApp = filter; for (Pattern pattern : compiledFilter.patterns) { if (pattern.matcher(attachment.description).find()) { matched = true; break; } } if (matched) break; } } } if (matched) { status.filteredByApp = compiledFilter.filter; break; // Stop checking other filters for this status } } } } if (statuses != null && !statuses.isEmpty()) { // Apply additional filters for HOME timeline if (filterTimeLineType == Timeline.TimeLineEnum.HOME) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); boolean groupReblogs = sharedpreferences.getBoolean(context.getString(R.string.SET_GROUP_REBLOGS), true); if (filterTimeLineType == Timeline.TimeLineEnum.HOME) { // Index seen reblog IDs for O(1) lookup java.util.HashSet<String> seenReblogIds = new java.util.HashSet<>(); for (int i = 0; i < statuses.size(); i++) { Status currentStatus = statuses.get(i); // Check filtered accounts if (filteredAccounts != null && !filteredAccounts.isEmpty()) { for (Account account : filteredAccounts) { if (account.acct.equals(statuses.get(i).account.acct) || (statuses.get(i).reblog != null && account.acct.equals(statuses.get(i).reblog.account.acct))) { if (account.acct.equals(currentStatus.account.acct) || (currentStatus.reblog != null && account.acct.equals(currentStatus.reblog.account.acct))) { Filter filterCustom = new Filter(); filterCustom.filter_action = "hide"; ArrayList<String> contextCustom = new ArrayList<>(); contextCustom.add("home"); filterCustom.title = "Fedilab"; filterCustom.context = contextCustom; statuses.get(i).filteredByApp = filterCustom; currentStatus.filteredByApp = filterCustom; break; } } } //Group boosts if (groupReblogs && statuses.get(i).filteredByApp == null && statuses.get(i).reblog != null) { for (int j = 0; j < i; j++) { if (statuses.get(j).reblog != null && statuses.get(j).reblog.id.equals(statuses.get(i).reblog.id)) { // Group duplicate reblogs using HashSet if (groupReblogs && currentStatus.filteredByApp == null && currentStatus.reblog != null) { if (seenReblogIds.contains(currentStatus.reblog.id)) { Filter filterCustom = new Filter(); filterCustom.filter_action = "hide"; ArrayList<String> contextCustom = new ArrayList<>(); contextCustom.add("home"); filterCustom.title = "Fedilab reblog"; filterCustom.context = contextCustom; statuses.get(i).filteredByApp = filterCustom; currentStatus.filteredByApp = filterCustom; } else { seenReblogIds.add(currentStatus.reblog.id); } } } } return statuses; } /** * Parse HTML content from status (cached per status) */ private static String parseStatusContent(Status status) { try { 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(); } // Append quote content if present if (status.reblog == null && status.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { content += Html.fromHtml(status.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); } else { content += Html.fromHtml(status.getQuote().content).toString(); } } else if (status.reblog != null && status.reblog.getQuote() != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { content += Html.fromHtml(status.reblog.getQuote().content, Html.FROM_HTML_MODE_LEGACY).toString(); } else { content += Html.fromHtml(status.reblog.getQuote().content).toString(); } } return content; } catch (Exception e) { return status.reblog != null ? status.reblog.content : status.content; } } /** * Parse HTML spoiler text from status */ private static String parseStatusSpoiler(Status status) { if (status.spoiler_text == null) { return null; } try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text, Html.FROM_HTML_MODE_LEGACY).toString(); } else { return Html.fromHtml(status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text).toString(); } } catch (Exception e) { return status.reblog != null ? status.reblog.spoiler_text : status.spoiler_text; } } /** * Helper class to hold precompiled filter patterns */ private static class CompiledFilter { final Filter filter; final List<Pattern> patterns; CompiledFilter(Filter filter, List<Pattern> patterns) { this.filter = filter; this.patterns = patterns; } return statuses; } /** Loading
app/src/main/java/app/fedilab/android/mastodon/ui/drawer/StatusAdapter.java +31 −8 Original line number Diff line number Diff line Loading @@ -1746,9 +1746,6 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> } else { measuredWidth = holder.binding.media.mediaContainer.getWidth(); } if (adapter != null && statusList != null) { adapter.notifyItemChanged(0, statusList.size()); } } }); } Loading Loading @@ -1939,6 +1936,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> player.setMediaSource(videoSource); player.prepare(); player.setPlayWhenReady(true); holder.activePlayers.add(player); } catch (Exception e) { e.printStackTrace(); } Loading @@ -1951,7 +1949,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> adapter.notifyItemChanged(position); if (timeout > 0) { new CountDownTimer((timeout * 1000L), 1000) { if (holder.activeTimer != null) { holder.activeTimer.cancel(); } holder.activeTimer = new CountDownTimer((timeout * 1000L), 1000) { public void onTick(long millisUntilFinished) { } Loading @@ -1959,7 +1960,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> statusToDeal.sensitive = true; adapter.notifyItemChanged(position); } }.start(); }; holder.activeTimer.start(); } return; } Loading Loading @@ -2032,6 +2034,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> player.setMediaSource(videoSource); player.prepare(); player.setPlayWhenReady(true); holder.activePlayers.add(player); } catch (Exception e) { e.printStackTrace(); } Loading @@ -2043,7 +2046,10 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> adapter.notifyItemChanged(position); if (timeout > 0) { new CountDownTimer((timeout * 1000L), 1000) { if (holder.activeTimer != null) { holder.activeTimer.cancel(); } holder.activeTimer = new CountDownTimer((timeout * 1000L), 1000) { public void onTick(long millisUntilFinished) { } Loading @@ -2051,7 +2057,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> statusToDeal.sensitive = true; adapter.notifyItemChanged(position); } }.start(); }; holder.activeTimer.start(); } return; } Loading Loading @@ -3910,7 +3917,21 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> public void onViewRecycled(@NonNull RecyclerView.ViewHolder viewHolder) { super.onViewRecycled(viewHolder); if (viewHolder instanceof StatusViewHolder holder) { //Release players //Cancel active timer if (holder.activeTimer != null) { holder.activeTimer.cancel(); holder.activeTimer = null; } //Release all tracked players for (ExoPlayer player : holder.activePlayers) { if (player != null) { player.release(); } } holder.activePlayers.clear(); //Release players in views (legacy cleanup) if (holder.binding != null) { //Cropped views if(holder.binding.media.getRoot().getChildCount() > 0) { for(int i = 0 ; i < holder.binding.media.getRoot().getChildCount() ; i++ ) { Loading Loading @@ -3950,6 +3971,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> DrawerStatusPixelfedBinding bindingPixelfed; DrawerStatusFilteredBinding bindingFiltered; DrawerStatusFilteredHideBinding bindingFilteredHide; List<ExoPlayer> activePlayers = new ArrayList<>(); CountDownTimer activeTimer; StatusViewHolder(DrawerStatusBinding itemView) { super(itemView.getRoot()); Loading