diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index c7a9c3487..4d28c1371 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -142,6 +142,7 @@ public class Helper { public static final String INSTANCE_SOCIAL_KEY = "jGj9gW3z9ptyIpB8CMGhAlTlslcemMV6AgoiImfw3vPP98birAJTHOWiu5ZWfCkLvcaLsFZw9e3Pb7TIwkbIyrj3z6S7r2oE6uy6EFHvls3YtapP8QKNZ980p9RfzTb4"; public static final String WEBSITE_VALUE = "https://fedilab.app"; + public static final String RECEIVE_TOAST_MESSAGE = "RECEIVE_TOAST_MESSAGE"; public static final String RECEIVE_TOAST_TYPE = "RECEIVE_TOAST_TYPE"; public static final String RECEIVE_TOAST_CONTENT = "RECEIVE_TOAST_CONTENT"; @@ -155,6 +156,8 @@ public class Helper { public static final String BROADCAST_DATA = "BROADCAST_DATA"; public static final String RECEIVE_REDRAW_TOPBAR = "RECEIVE_REDRAW_TOPBAR"; + public static final String RECEIVE_STATUS_ACTION = "RECEIVE_STATUS_ACTION"; + public static final String RECEIVE_RECREATE_ACTIVITY = "RECEIVE_RECREATE_ACTIVITY"; public static final String RECEIVE_MASTODON_LIST = "RECEIVE_MASTODON_LIST"; public static final String RECEIVE_REDRAW_PROFILE = "RECEIVE_REDRAW_PROFILE"; @@ -163,6 +166,10 @@ public class Helper { public static final String ARG_NOTIFICATION_TYPE = "ARG_NOTIFICATION_TYPE"; public static final String ARG_EXCLUDED_NOTIFICATION_TYPE = "ARG_EXCLUDED_NOTIFICATION_TYPE"; public static final String ARG_STATUS = "ARG_STATUS"; + public static final String ARG_STATUS_DELETED = "ARG_STATUS_DELETED"; + public static final String ARG_STATUS_ACTION = "ARG_STATUS_ACTION"; + public static final String ARG_STATUS_ACCOUNT_ID_DELETED = "ARG_STATUS_ACCOUNT_ID_DELETED"; + public static final String ARG_STATUS_DRAFT = "ARG_STATUS_DRAFT"; public static final String ARG_STATUS_SCHEDULED = "ARG_STATUS_SCHEDULED"; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 93fc1dbc8..ad2364117 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -53,6 +53,7 @@ import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.PopupMenu; @@ -62,6 +63,7 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; import androidx.recyclerview.widget.RecyclerView; @@ -410,6 +412,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.reblogged = _status.reblogged; statusToDeal.reblogs_count = _status.reblogs_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } else { @@ -422,6 +425,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.reblogged = _status.reblogged; statusToDeal.reblogs_count = _status.reblogs_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } else { @@ -430,6 +434,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.reblogged = _status.reblogged; statusToDeal.reblogs_count = _status.reblogs_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } @@ -445,6 +450,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.reblogged = _status.reblogged; statusToDeal.reblogs_count = _status.reblogs_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, status)); }); } else { @@ -453,6 +459,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.reblogged = _status.reblogged; statusToDeal.reblogs_count = _status.reblogs_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, status)); }); } @@ -467,9 +474,7 @@ public class StatusAdapter extends RecyclerView.Adapter if (confirmFav) { AlertDialog.Builder alt_bld = new AlertDialog.Builder(context, Helper.dialogStyle()); alt_bld.setMessage(context.getString(R.string.favourite_add)); - alt_bld.setPositiveButton(R.string.yes, (dialog, id) -> { - CrossActionHelper.doCrossAction(context, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, null, statusToDeal); - }); + alt_bld.setPositiveButton(R.string.yes, (dialog, id) -> CrossActionHelper.doCrossAction(context, CrossActionHelper.TypeOfCrossAction.FAVOURITE_ACTION, null, statusToDeal)); alt_bld.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); AlertDialog alert = alt_bld.create(); alert.show(); @@ -497,6 +502,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.favourited = _status.favourited; statusToDeal.favourites_count = _status.favourites_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } else { @@ -517,6 +523,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.favourited = _status.favourited; statusToDeal.favourites_count = _status.favourites_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } @@ -532,6 +539,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.favourited = _status.favourited; statusToDeal.favourites_count = _status.favourites_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } else { @@ -540,6 +548,7 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, _status -> { statusToDeal.favourited = _status.favourited; statusToDeal.favourites_count = _status.favourites_count; + sendAction(context, Helper.ARG_STATUS_ACTION, statusToDeal, null); adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); }); } @@ -968,19 +977,17 @@ public class StatusAdapter extends RecyclerView.Adapter } }); } - holder.binding.poll.refreshPoll.setOnClickListener(v -> { - statusesVM.getPoll(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.poll.id) - .observe((LifecycleOwner) context, poll -> { - //Store span elements - int i = 0; - for (Poll.PollItem item : statusToDeal.poll.options) { - poll.options.get(i).span_title = item.span_title; - i++; - } - statusToDeal.poll = poll; - adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); - }); - }); + holder.binding.poll.refreshPoll.setOnClickListener(v -> statusesVM.getPoll(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.poll.id) + .observe((LifecycleOwner) context, poll -> { + //Store span elements + int i = 0; + for (Poll.PollItem item : statusToDeal.poll.options) { + poll.options.get(i).span_title = item.span_title; + i++; + } + statusToDeal.poll = poll; + adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); + })); holder.binding.poll.pollContainer.setVisibility(View.VISIBLE); String pollInfo = context.getResources().getQuantityString(R.plurals.number_of_voters, statusToDeal.poll.voters_count, statusToDeal.poll.voters_count); if (statusToDeal.poll.expired) { @@ -1105,6 +1112,7 @@ public class StatusAdapter extends RecyclerView.Adapter statusDraft.statusDraftList.add(statusDeleted); intent.putExtra(Helper.ARG_STATUS_DRAFT, statusDraft); context.startActivity(intent); + sendAction(context, Helper.ARG_STATUS_DELETED, statusToDeal, null); }); } }); @@ -1121,16 +1129,19 @@ public class StatusAdapter extends RecyclerView.Adapter } else if (itemId == R.id.action_remove) { AlertDialog.Builder builderInner = new AlertDialog.Builder(context, Helper.dialogStyle()); builderInner.setTitle(stringArrayConf[0]); - builderInner.setMessage(statusToDeal.text); + builderInner.setMessage(statusToDeal.content); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + builderInner.setMessage(Html.fromHtml(statusToDeal.content, Html.FROM_HTML_MODE_LEGACY).toString()); + else + builderInner.setMessage(Html.fromHtml(statusToDeal.content).toString()); builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); - builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { - statusesVM.deleteStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id) - .observe((LifecycleOwner) context, statusDeleted -> { - statusList.remove(statusToDeal); - int position = getPositionAsync(notificationList, statusList, status); - adapter.notifyItemRemoved(position); - }); - }); + builderInner.setPositiveButton(R.string.yes, (dialog, which) -> statusesVM.deleteStatus(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id) + .observe((LifecycleOwner) context, statusDeleted -> { + int position = getPositionAsync(notificationList, statusList, status); + statusList.remove(statusToDeal); + adapter.notifyItemRemoved(position); + sendAction(context, Helper.ARG_STATUS_DELETED, statusToDeal, null); + })); builderInner.show(); } else if (itemId == R.id.action_block_domain) { AlertDialog.Builder builderInner = new AlertDialog.Builder(context, Helper.dialogStyle()); @@ -1148,35 +1159,26 @@ public class StatusAdapter extends RecyclerView.Adapter builderInner.setTitle(stringArrayConf[0]); builderInner.setMessage(statusToDeal.account.acct); builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); - builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { - accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id, null, null) - .observe((LifecycleOwner) context, relationShip -> { - Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_LONG).show(); - }); - }); + builderInner.setPositiveButton(R.string.yes, (dialog, which) -> accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id, null, null) + .observe((LifecycleOwner) context, relationShip -> { + sendAction(context, Helper.ARG_STATUS_ACCOUNT_ID_DELETED, null, statusToDeal.account.id); + Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_LONG).show(); + })); builderInner.show(); } else if (itemId == R.id.action_mute_conversation) { if (statusToDeal.muted) { - statusesVM.unMute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> { - Toasty.info(context, context.getString(R.string.toast_unmute_conversation)).show(); - }); + statusesVM.unMute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> Toasty.info(context, context.getString(R.string.toast_unmute_conversation)).show()); } else { - statusesVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> { - Toasty.info(context, context.getString(R.string.toast_mute_conversation)).show(); - }); + statusesVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> Toasty.info(context, context.getString(R.string.toast_mute_conversation)).show()); } return true; } else if (itemId == R.id.action_bookmark) { if (statusToDeal.bookmarked) { - statusesVM.unBookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> { - Toasty.info(context, context.getString(R.string.status_unbookmarked)).show(); - }); + statusesVM.unBookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> Toasty.info(context, context.getString(R.string.status_unbookmarked)).show()); } else { - statusesVM.bookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> { - Toasty.info(context, context.getString(R.string.status_bookmarked)).show(); - }); + statusesVM.bookmark(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> Toasty.info(context, context.getString(R.string.status_bookmarked)).show()); } } else if (itemId == R.id.action_timed_mute) { MastodonHelper.scheduleBoost(context, MastodonHelper.ScheduleType.TIMED_MUTED, statusToDeal, null, null); @@ -1186,12 +1188,11 @@ public class StatusAdapter extends RecyclerView.Adapter builderInner.setTitle(stringArrayConf[1]); builderInner.setMessage(statusToDeal.account.acct); builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); - builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { - accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id) - .observe((LifecycleOwner) context, relationShip -> { - Toasty.info(context, context.getString(R.string.toast_block)).show(); - }); - }); + builderInner.setPositiveButton(R.string.yes, (dialog, which) -> accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id) + .observe((LifecycleOwner) context, relationShip -> { + sendAction(context, Helper.ARG_STATUS_ACCOUNT_ID_DELETED, null, statusToDeal.account.id); + Toasty.info(context, context.getString(R.string.toast_block)).show(); + })); builderInner.show(); } else if (itemId == R.id.action_translate) { MyTransL.translatorEngine et = MyTransL.translatorEngine.LIBRETRANSLATE; @@ -1301,9 +1302,7 @@ public class StatusAdapter extends RecyclerView.Adapter if (holder.bindingReport != null) { holder.bindingReport.checkbox.setChecked(status.isChecked); - holder.bindingReport.checkbox.setOnClickListener(v -> { - status.isChecked = !status.isChecked; - }); + holder.bindingReport.checkbox.setOnClickListener(v -> status.isChecked = !status.isChecked); } } @@ -1322,6 +1321,27 @@ public class StatusAdapter extends RecyclerView.Adapter } } + /** + * Send a broadcast to other open fragments that content a timeline + * + * @param context - Context + * @param type - String type for the broadCast (Helper.ARG_STATUS_ACTION / Helper.ARG_STATUS_ACCOUNT_ID_DELETED / Helper.ARG_STATUS_DELETED ) + * @param status - Status that is sent (can be null) + * @param id - Id of an account (can be null) + */ + private static void sendAction(@NonNull Context context, @NonNull String type, @Nullable Status status, @Nullable String id) { + Bundle b = new Bundle(); + if (status != null) { + b.putSerializable(type, status); + } + if (id != null) { + b.putSerializable(type, id); + } + Intent intentBC = new Intent(Helper.RECEIVE_STATUS_ACTION); + intentBC.putExtras(b); + LocalBroadcastManager.getInstance(context).sendBroadcast(intentBC); + } + /** * Will manage the current position of the element in the adapter. Action is async, and position might have changed * @@ -1330,7 +1350,7 @@ public class StatusAdapter extends RecyclerView.Adapter * @param status Status - Current status * @return int - position in real time */ - private static int getPositionAsync(List notificationList, List statusList, Status status) { + public static int getPositionAsync(List notificationList, List statusList, Status status) { int position = 0; if (statusList != null) { for (Status _status : statusList) { diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java index 3fc8eeea9..074cbb825 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -17,6 +17,10 @@ package app.fedilab.android.ui.fragment.timeline; import static app.fedilab.android.BaseMainActivity.networkAvailable; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -28,6 +32,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -73,6 +78,67 @@ public class FragmentMastodonTimeline extends Fragment { private boolean exclude_replies, exclude_reblogs, show_pinned, media_only, minified; private String viewModelKey, remoteInstance; + //Handle actions that can be done in other fragments + private final BroadcastReceiver receive_action = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Bundle b = intent.getExtras(); + if (b != null) { + Status receivedStatus = (Status) b.getSerializable(Helper.ARG_STATUS_ACTION); + String delete_statuses_for_user = b.getString(Helper.ARG_STATUS_ACCOUNT_ID_DELETED); + Status status_to_delete = (Status) b.getSerializable(Helper.ARG_STATUS_DELETED); + if (receivedStatus != null && statusAdapter != null) { + int position = getPosition(receivedStatus); + if (position >= 0) { + statuses.get(position).reblog = receivedStatus.reblog; + statuses.get(position).favourited = receivedStatus.favourited; + statuses.get(position).bookmarked = receivedStatus.bookmarked; + statusAdapter.notifyItemChanged(position); + } + } else if (delete_statuses_for_user != null && statusAdapter != null) { + List statusesToRemove = new ArrayList<>(); + for (Status status : statuses) { + if (status.account.id.equals(delete_statuses_for_user)) { + statusesToRemove.add(status); + } + } + for (Status statusToRemove : statusesToRemove) { + int position = getPosition(statusToRemove); + if (position >= 0) { + statuses.remove(position); + statusAdapter.notifyItemRemoved(position); + } + } + } else if (status_to_delete != null && statusAdapter != null) { + int position = getPosition(status_to_delete); + if (position >= 0) { + statuses.remove(position); + statusAdapter.notifyItemRemoved(position); + } + } + } + } + }; + + /** + * Return the position of the status in the ArrayList + * + * @param status - Status to fetch + * @return position or -1 if not found + */ + private int getPosition(Status status) { + int position = 0; + boolean found = false; + for (Status _status : statuses) { + if (_status.id.compareTo(status.id) == 0) { + found = true; + break; + } + position++; + } + return found ? position : -1; + } + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -94,6 +160,7 @@ public class FragmentMastodonTimeline extends Fragment { minified = getArguments().getBoolean(Helper.ARG_MINIFIED, false); statusReport = (Status) getArguments().getSerializable(Helper.ARG_STATUS_REPORT); } + LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION)); binding = FragmentPaginationBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -237,12 +304,8 @@ public class FragmentMastodonTimeline extends Fragment { } binding.loadingNextElements.setVisibility(View.GONE); if (statuses != null && fetched_statuses != null && fetched_statuses.statuses != null) { - int startId = 0; //There are some statuses present in the timeline - if (statuses.size() > 0) { - startId = statuses.size(); - } - + int startId = statuses.size(); if (direction == DIRECTION.TOP) { statuses.addAll(0, fetched_statuses.statuses); statusAdapter.notifyItemRangeInserted(0, fetched_statuses.statuses.size()); @@ -273,6 +336,7 @@ public class FragmentMastodonTimeline extends Fragment { if (binding != null) { binding.recyclerView.setAdapter(null); } + LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(receive_action); statusAdapter = null; binding = null; }