diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index a09a6ec54..ecfd4c635 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -683,6 +683,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt regex_local = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_LOCAL) + currentUserID + currentInstance, null); regex_public = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_PUBLIC) + currentUserID + currentInstance, null); show_art_nsfw = sharedpreferences.getBoolean(getString(R.string.SET_ART_WITH_NSFW) + currentUserID + currentInstance, false); + binding.profilePicture.setOnClickListener(v -> binding.drawerLayout.openDrawer(GravityCompat.START)); Helper.loadPP(BaseMainActivity.this, binding.profilePicture, currentAccount); headerMainBinding.accountAcc.setText(String.format("%s@%s", currentAccount.mastodon_account.username, currentAccount.instance)); diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/BubbleTimeline.java b/app/src/main/java/app/fedilab/android/client/entities/app/BubbleTimeline.java new file mode 100644 index 000000000..b871af44c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/app/BubbleTimeline.java @@ -0,0 +1,35 @@ +package app.fedilab.android.client.entities.app; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.List; + +public class BubbleTimeline implements Serializable { + @SerializedName("id") + public int id; + @SerializedName("only_media") + public boolean only_media = false; + @SerializedName("remote") + public boolean remote = false; + @SerializedName("with_muted") + public boolean with_muted; + @SerializedName("exclude_visibilities") + public List exclude_visibilities = null; + @SerializedName("reply_visibility") + public String reply_visibility = null; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/PinnedTimeline.java b/app/src/main/java/app/fedilab/android/client/entities/app/PinnedTimeline.java index 3bfa487b5..1158f816b 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/PinnedTimeline.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/PinnedTimeline.java @@ -38,6 +38,8 @@ public class PinnedTimeline implements Serializable { public RemoteInstance remoteInstance; @SerializedName("tagTimeline") public TagTimeline tagTimeline; + @SerializedName("bubbleTimeline") + public BubbleTimeline bubbleTimeline; @SerializedName("mastodonList") public MastodonList mastodonList; @SerializedName("currentFilter") 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 cb02fdb93..5af0b7272 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -257,6 +257,7 @@ public class Helper { public static final String ARG_SEARCH_KEYWORD_CACHE = "ARG_SEARCH_KEYWORD_CACHE"; public static final String ARG_VIEW_MODEL_KEY = "ARG_VIEW_MODEL_KEY"; public static final String ARG_TAG_TIMELINE = "ARG_TAG_TIMELINE"; + public static final String ARG_BUBBLE_TIMELINE = "ARG_BUBBLE_TIMELINE"; public static final String ARG_MEDIA_POSITION = "ARG_MEDIA_POSITION"; public static final String ARG_MEDIA_ATTACHMENT = "ARG_MEDIA_ATTACHMENT"; public static final String ARG_MEDIA_ATTACHMENTS = "ARG_MEDIA_ATTACHMENTS"; diff --git a/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java b/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java index 2deefd41c..9457b9c5d 100644 --- a/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/PinnedTimelineHelper.java @@ -14,6 +14,7 @@ package app.fedilab.android.helper; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ + import static app.fedilab.android.BaseMainActivity.currentAccount; import static app.fedilab.android.BaseMainActivity.currentInstance; import static app.fedilab.android.BaseMainActivity.currentUserID; @@ -59,6 +60,7 @@ import app.fedilab.android.R; import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.entities.api.MastodonList; import app.fedilab.android.client.entities.app.BottomMenu; +import app.fedilab.android.client.entities.app.BubbleTimeline; import app.fedilab.android.client.entities.app.Pinned; import app.fedilab.android.client.entities.app.PinnedTimeline; import app.fedilab.android.client.entities.app.RemoteInstance; @@ -66,6 +68,8 @@ import app.fedilab.android.client.entities.app.StatusCache; import app.fedilab.android.client.entities.app.TagTimeline; import app.fedilab.android.client.entities.app.Timeline; import app.fedilab.android.databinding.ActivityMainBinding; +import app.fedilab.android.databinding.DialogBubbleExcludeVisibilityBinding; +import app.fedilab.android.databinding.DialogBubbleReplyVisibilityBinding; import app.fedilab.android.databinding.TabCustomDefaultViewBinding; import app.fedilab.android.databinding.TabCustomViewBinding; import app.fedilab.android.exception.DBException; @@ -498,6 +502,9 @@ public class PinnedTimelineHelper { case TAG: tagClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString()); break; + case BUBBLE: + bubbleClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString()); + break; case REMOTE: if (pinnedTimelineVisibleList.get(position).remoteInstance.type != RemoteInstance.InstanceType.NITTER) { instanceClick(activity, finalPinned, v, activityMainBinding, finalI, activityMainBinding.tabLayout.getTabAt(finalI).getTag().toString()); @@ -980,6 +987,244 @@ public class PinnedTimelineHelper { } + /** + * Manage long clicks on Bubble timelines + * + * @param activity - BaseMainActivity activity + * @param pinned - {@link Pinned} + * @param view - View + * @param position - int position of the tab + */ + public static void bubbleClick(BaseMainActivity activity, Pinned pinned, View view, ActivityMainBinding activityMainBinding, int position, String slug) { + int toRemove = itemToRemoveInBottomMenu(activity); + PopupMenu popup = new PopupMenu(activity, view); + int offSetPosition = position - (BOTTOM_TIMELINE_COUNT - toRemove); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity); + boolean singleBar = sharedpreferences.getBoolean(activity.getString(R.string.SET_USE_SINGLE_TOPBAR), false); + if (singleBar) { + offSetPosition = position; + } + + if (pinned.pinnedTimelines.get(offSetPosition).bubbleTimeline == null) { + pinned.pinnedTimelines.get(offSetPosition).bubbleTimeline = new BubbleTimeline(); + } + BubbleTimeline bubbleTimeline = pinned.pinnedTimelines.get(offSetPosition).bubbleTimeline; + + popup.getMenuInflater() + .inflate(R.menu.option_bubble_timeline, popup.getMenu()); + Menu menu = popup.getMenu(); + + final MenuItem itemMediaOnly = menu.findItem(R.id.action_show_media_only); + final MenuItem itemRemote = menu.findItem(R.id.action_remote); + + + final boolean[] changes = {false}; + final boolean[] mediaOnly = {false}; + final boolean[] remote = {false}; + mediaOnly[0] = bubbleTimeline.only_media; + remote[0] = bubbleTimeline.remote; + itemMediaOnly.setChecked(mediaOnly[0]); + itemRemote.setChecked(remote[0]); + popup.setOnDismissListener(menu1 -> { + if (changes[0]) { + if (activityMainBinding.viewPager.getAdapter() != null) { + try { + new StatusCache(activity).deleteForSlug(slug); + } catch (DBException e) { + e.printStackTrace(); + } + + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(activity.getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); + editor.commit(); + Fragment fragmentMastodonTimeline = (Fragment) activityMainBinding.viewPager.getAdapter().instantiateItem(activityMainBinding.viewPager, activityMainBinding.tabLayout.getSelectedTabPosition()); + if (fragmentMastodonTimeline instanceof FragmentMastodonTimeline && fragmentMastodonTimeline.isVisible()) { + FragmentTransaction fragTransaction = activity.getSupportFragmentManager().beginTransaction(); + fragTransaction.detach(fragmentMastodonTimeline).commit(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.BUBBLE); + bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, bubbleTimeline); + bundle.putSerializable(Helper.ARG_INITIALIZE_VIEW, false); + fragmentMastodonTimeline.setArguments(bundle); + FragmentTransaction fragTransaction2 = activity.getSupportFragmentManager().beginTransaction(); + fragTransaction2.attach(fragmentMastodonTimeline); + fragTransaction2.commit(); + ((FragmentMastodonTimeline) fragmentMastodonTimeline).recreate(); + } + } + } + }); + + + int finalOffSetPosition = offSetPosition; + popup.setOnMenuItemClickListener(item -> { + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW); + item.setActionView(new View(activity)); + item.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return false; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + return false; + } + }); + changes[0] = true; + int itemId = item.getItemId(); + if (itemId == R.id.action_show_media_only) { + mediaOnly[0] = !mediaOnly[0]; + bubbleTimeline.only_media = mediaOnly[0]; + pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline; + itemMediaOnly.setChecked(mediaOnly[0]); + try { + new Pinned(activity).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + } else if (itemId == R.id.action_remote) { + remote[0] = !remote[0]; + bubbleTimeline.remote = remote[0]; + pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline; + itemRemote.setChecked(remote[0]); + try { + new Pinned(activity).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + } else if (itemId == R.id.action_exclude_visibility) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle()); + DialogBubbleExcludeVisibilityBinding dialogBinding = DialogBubbleExcludeVisibilityBinding.inflate(activity.getLayoutInflater()); + dialogBuilder.setView(dialogBinding.getRoot()); + dialogBuilder.setTitle(R.string.exclude_visibility); + if (bubbleTimeline.exclude_visibilities == null) { + bubbleTimeline.exclude_visibilities = new ArrayList<>(); + } + for (String value : bubbleTimeline.exclude_visibilities) { + if (value.equalsIgnoreCase("public")) { + dialogBinding.valuePublic.setChecked(true); + } + if (value.equalsIgnoreCase("local")) { + dialogBinding.valueLocal.setChecked(true); + } + if (value.equalsIgnoreCase("direct")) { + dialogBinding.valueDirect.setChecked(true); + } + if (value.equalsIgnoreCase("list")) { + dialogBinding.valueList.setChecked(true); + } + if (value.equalsIgnoreCase("private")) { + dialogBinding.valuePrivate.setChecked(true); + } + if (value.equalsIgnoreCase("unlisted")) { + dialogBinding.valueUnlisted.setChecked(true); + } + } + dialogBinding.valuePrivate.setOnCheckedChangeListener((compoundButton, checked) -> { + if (checked) { + if (!bubbleTimeline.exclude_visibilities.contains("private")) { + bubbleTimeline.exclude_visibilities.add("private"); + } + } else { + bubbleTimeline.exclude_visibilities.remove("private"); + } + }); + dialogBinding.valueDirect.setOnCheckedChangeListener((compoundButton, checked) -> { + if (checked) { + if (!bubbleTimeline.exclude_visibilities.contains("direct")) { + bubbleTimeline.exclude_visibilities.add("direct"); + } + } else { + bubbleTimeline.exclude_visibilities.remove("direct"); + } + }); + dialogBinding.valueList.setOnCheckedChangeListener((compoundButton, checked) -> { + if (checked) { + if (!bubbleTimeline.exclude_visibilities.contains("list")) { + bubbleTimeline.exclude_visibilities.add("list"); + } + } else { + bubbleTimeline.exclude_visibilities.remove("list"); + } + }); + dialogBinding.valueLocal.setOnCheckedChangeListener((compoundButton, checked) -> { + if (checked) { + if (!bubbleTimeline.exclude_visibilities.contains("local")) { + bubbleTimeline.exclude_visibilities.add("local"); + } + } else { + bubbleTimeline.exclude_visibilities.remove("local"); + } + }); + dialogBinding.valuePublic.setOnCheckedChangeListener((compoundButton, checked) -> { + if (checked) { + if (!bubbleTimeline.exclude_visibilities.contains("public")) { + bubbleTimeline.exclude_visibilities.add("public"); + } + } else { + bubbleTimeline.exclude_visibilities.remove("public"); + } + }); + dialogBinding.valueUnlisted.setOnCheckedChangeListener((compoundButton, checked) -> { + if (checked) { + if (!bubbleTimeline.exclude_visibilities.contains("unlisted")) { + bubbleTimeline.exclude_visibilities.add("unlisted"); + } + } else { + bubbleTimeline.exclude_visibilities.remove("unlisted"); + } + }); + dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { + pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline; + try { + new Pinned(activity).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + }); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + } else if (itemId == R.id.action_reply_visibility) { + AlertDialog.Builder dialogBuilder; + AlertDialog alertDialog; + dialogBuilder = new AlertDialog.Builder(activity, Helper.dialogStyle()); + DialogBubbleReplyVisibilityBinding dialogBinding = DialogBubbleReplyVisibilityBinding.inflate(activity.getLayoutInflater()); + dialogBuilder.setView(dialogBinding.getRoot()); + dialogBuilder.setTitle(R.string.reply_visibility); + int checkedId = R.id.all; + if (bubbleTimeline.reply_visibility != null && bubbleTimeline.reply_visibility.equalsIgnoreCase("following")) { + checkedId = R.id.following; + } else if (bubbleTimeline.reply_visibility != null && bubbleTimeline.reply_visibility.equalsIgnoreCase("self")) { + checkedId = R.id.self; + } + dialogBinding.replyVisibility.check(checkedId); + dialogBinding.replyVisibility.setOnCheckedChangeListener((radioGroup, checkedElement) -> { + if (checkedElement == R.id.all) { + bubbleTimeline.reply_visibility = null; + } else if (checkedElement == R.id.following) { + bubbleTimeline.reply_visibility = "following"; + } else if (checkedElement == R.id.self) { + bubbleTimeline.reply_visibility = "self"; + } + }); + dialogBuilder.setPositiveButton(R.string.validate, (dialog, id) -> { + pinned.pinnedTimelines.get(finalOffSetPosition).bubbleTimeline = bubbleTimeline; + try { + new Pinned(activity).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + }); + alertDialog = dialogBuilder.create(); + alertDialog.show(); + } + return false; + }); + popup.show(); + } + + /** * Manage long clicks on followed instances * 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 641a94ee3..cc7bdf6f5 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 @@ -51,6 +51,7 @@ import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.Pagination; import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Statuses; +import app.fedilab.android.client.entities.app.BubbleTimeline; import app.fedilab.android.client.entities.app.PinnedTimeline; import app.fedilab.android.client.entities.app.RemoteInstance; import app.fedilab.android.client.entities.app.StatusCache; @@ -165,6 +166,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. private Statuses initialStatuses; private String list_id; private TagTimeline tagTimeline; + private BubbleTimeline bubbleTimeline; private LinearLayoutManager mLayoutManager; private Account accountTimeline; private boolean exclude_replies, exclude_reblogs, show_pinned, media_only, minified; @@ -331,6 +333,7 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. isViewInitialized = getArguments().getBoolean(Helper.ARG_INITIALIZE_VIEW, true); isNotPinnedTimeline = isViewInitialized; tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE); + bubbleTimeline = (BubbleTimeline) getArguments().getSerializable(Helper.ARG_BUBBLE_TIMELINE); accountTimeline = (Account) getArguments().getSerializable(Helper.ARG_ACCOUNT); exclude_replies = !getArguments().getBoolean(Helper.ARG_SHOW_REPLIES, true); checkRemotely = getArguments().getBoolean(Helper.ARG_CHECK_REMOTELY, false); @@ -354,6 +357,8 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. if (tagTimeline.isART) { timelineType = Timeline.TimeLineEnum.ART; } + } else if (bubbleTimeline != null) { + ident = "@B@Bubble"; } else if (list_id != null) { ident = "@l@" + list_id; } else if (remoteInstance != null && !checkRemotely) { @@ -713,8 +718,10 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. timelineParams.remote = true; break; case BUBBLE: - timelineParams.onlyMedia = false; - timelineParams.remote = false; + timelineParams.onlyMedia = bubbleTimeline.only_media; + timelineParams.remote = bubbleTimeline.remote; + timelineParams.replyVisibility = bubbleTimeline.reply_visibility; + timelineParams.excludeVisibilities = bubbleTimeline.exclude_visibilities; break; case LIST: timelineParams.listId = list_id; diff --git a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java index aada3c696..326b3c654 100644 --- a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabPageAdapter.java @@ -137,6 +137,8 @@ public class FedilabPageAdapter extends FragmentStatePagerAdapter { bundle.putSerializable(Helper.ARG_TAG_TIMELINE, pinnedTimeline.tagTimeline); } else if (pinnedTimeline.type == Timeline.TimeLineEnum.REMOTE) { bundle.putSerializable(Helper.ARG_REMOTE_INSTANCE, pinnedTimeline); + } else if (pinnedTimeline.type == Timeline.TimeLineEnum.BUBBLE) { + bundle.putSerializable(Helper.ARG_BUBBLE_TIMELINE, pinnedTimeline.bubbleTimeline); } } diff --git a/app/src/main/res/drawable/ic_baseline_bubble_chart_24.xml b/app/src/main/res/drawable/ic_baseline_bubble_chart_24.xml index 86b72699d..8850e91f8 100644 --- a/app/src/main/res/drawable/ic_baseline_bubble_chart_24.xml +++ b/app/src/main/res/drawable/ic_baseline_bubble_chart_24.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_bubble_reply_visibility.xml b/app/src/main/res/layout/dialog_bubble_reply_visibility.xml new file mode 100644 index 000000000..7739fb3d2 --- /dev/null +++ b/app/src/main/res/layout/dialog_bubble_reply_visibility.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/option_bubble_timeline.xml b/app/src/main/res/menu/option_bubble_timeline.xml new file mode 100644 index 000000000..e3b5c1eeb --- /dev/null +++ b/app/src/main/res/menu/option_bubble_timeline.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e3256ec44..dec946dd3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1365,6 +1365,8 @@ SET_LED_COLOUR_VAL_N SET_SHOW_BOOSTS SET_SHOW_REPLIES + + SET_DISABLE_ANIMATED_EMOJI SET_CAPITALIZE SET_THEME_BASE @@ -2175,4 +2177,9 @@ Display the \"Quote\" button Display \"Reactions\" buttons Bubble + Exclude visibility + Reply visibility + List + Following + Self \ No newline at end of file