support exclusive lists

closes sk22#576
This commit is contained in:
sk
2023-06-15 19:21:26 +02:00
parent b463ef65ce
commit 6595a088fb
12 changed files with 112 additions and 24 deletions

View File

@@ -4,16 +4,18 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class CreateList extends MastodonAPIRequest<ListTimeline> {
public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) {
public CreateList(String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.POST, "/lists", ListTimeline.class);
Request req = new Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
}
public static class Request {
public String title;
public boolean exclusive;
public ListTimeline.RepliesPolicy repliesPolicy;
}
}

View File

@@ -4,10 +4,11 @@ import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.ListTimeline;
public class UpdateList extends MastodonAPIRequest<ListTimeline> {
public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
public UpdateList(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class);
CreateList.Request req = new CreateList.Request();
req.title = title;
req.exclusive = exclusive;
req.repliesPolicy = repliesPolicy;
setRequestBody(req);
}

View File

@@ -6,10 +6,12 @@ public class ListUpdatedCreatedEvent {
public final String id;
public final String title;
public final ListTimeline.RepliesPolicy repliesPolicy;
public final boolean exclusive;
public ListUpdatedCreatedEvent(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) {
public ListUpdatedCreatedEvent(String id, String title, boolean exclusive, ListTimeline.RepliesPolicy repliesPolicy) {
this.id = id;
this.title = title;
this.exclusive = exclusive;
this.repliesPolicy = repliesPolicy;
}
}

View File

@@ -492,6 +492,7 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab
} else if ((list = listItems.get(id)) != null) {
args.putString("listID", list.id);
args.putString("listTitle", list.title);
args.putBoolean("listIsExclusive", list.exclusive);
if (list.repliesPolicy != null) args.putInt("repliesPolicy", list.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
} else if ((hashtag = hashtagsItems.get(id)) != null) {

View File

@@ -24,7 +24,7 @@ import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.model.TimelineDefinition;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.ListTimelineEditor;
import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.StatusFilterPredicate;
import java.util.List;
@@ -36,12 +36,12 @@ import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.utils.V;
public class ListTimelineFragment extends PinnableStatusListFragment {
private String listID;
private String listTitle;
@Nullable
private ListTimeline.RepliesPolicy repliesPolicy;
private boolean exclusive;
@Override
protected boolean wantsComposeButton() {
@@ -54,6 +54,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
Bundle args = getArguments();
listID = args.getString("listID");
listTitle = args.getString("listTitle");
exclusive = args.getBoolean("listIsExclusive");
repliesPolicy = ListTimeline.RepliesPolicy.values()[args.getInt("repliesPolicy", 0)];
setTitle(listTitle);
@@ -88,8 +89,8 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
public boolean onOptionsItemSelected(MenuItem item) {
if (super.onOptionsItemSelected(item)) return true;
if (item.getItemId() == R.id.edit) {
ListTimelineEditor editor = new ListTimelineEditor(getContext());
editor.applyList(listTitle, repliesPolicy);
ListEditor editor = new ListEditor(getContext());
editor.applyList(listTitle, exclusive, repliesPolicy);
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_edit_list_title)
.setIcon(R.drawable.ic_fluent_people_28_regular)
@@ -97,14 +98,15 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
.setPositiveButton(R.string.save, (d, which) -> {
String newTitle = editor.getTitle().trim();
setTitle(newTitle);
new UpdateList(listID, newTitle, editor.getRepliesPolicy()).setCallback(new Callback<>() {
new UpdateList(listID, newTitle, editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline list) {
if (getActivity() == null) return;
setTitle(list.title);
listTitle = list.title;
repliesPolicy = list.repliesPolicy;
E.post(new ListUpdatedCreatedEvent(listID, listTitle, repliesPolicy));
exclusive = list.exclusive;
E.post(new ListUpdatedCreatedEvent(listID, listTitle, exclusive, repliesPolicy));
}
@Override
@@ -127,7 +129,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
@Override
protected TimelineDefinition makeTimelineDefinition() {
return TimelineDefinition.ofList(listID, listTitle);
return TimelineDefinition.ofList(listID, listTitle, exclusive);
}
@Override

View File

@@ -27,7 +27,7 @@ import org.joinmastodon.android.events.ListUpdatedCreatedEvent;
import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.ui.DividerItemDecoration;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.views.ListTimelineEditor;
import org.joinmastodon.android.ui.views.ListEditor;
import org.joinmastodon.android.utils.ProvidesAssistContent;
import java.util.ArrayList;
@@ -91,18 +91,18 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.create) {
ListTimelineEditor editor = new ListTimelineEditor(getContext());
ListEditor editor = new ListEditor(getContext());
new M3AlertDialogBuilder(getActivity())
.setTitle(R.string.sk_create_list_title)
.setIcon(R.drawable.ic_fluent_people_add_28_regular)
.setView(editor)
.setPositiveButton(R.string.sk_create, (d, which) ->
new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
new CreateList(editor.getTitle(), editor.isExclusive(), editor.getRepliesPolicy()).setCallback(new Callback<>() {
@Override
public void onSuccess(ListTimeline list) {
data.add(0, list);
adapter.notifyItemRangeInserted(0, 1);
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.repliesPolicy));
E.post(new ListUpdatedCreatedEvent(list.id, list.title, list.exclusive, list.repliesPolicy));
}
@Override
@@ -185,6 +185,7 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
if (item.id.equals(event.id)) {
item.title = event.title;
item.repliesPolicy = event.repliesPolicy;
item.exclusive = event.exclusive;
adapter.notifyItemChanged(i);
break;
}
@@ -242,7 +243,9 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
@Override
public void onBind(ListTimeline item) {
title.setText(item.title);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(R.drawable.ic_fluent_people_24_regular), null, null, null);
title.setCompoundDrawablesRelativeWithIntrinsicBounds(itemView.getContext().getDrawable(
item.exclusive ? R.drawable.ic_fluent_rss_24_regular : R.drawable.ic_fluent_people_24_regular
), null, null, null);
if (profileAccountId != null) {
Boolean checked = userInList.get(item.id);
listToggle.setVisibility(View.VISIBLE);
@@ -263,6 +266,7 @@ public class ListsFragment extends RecyclerFragment<ListTimeline> implements Scr
args.putString("account", accountID);
args.putString("listID", item.id);
args.putString("listTitle", item.title);
args.putBoolean("listIsExclusive", item.exclusive);
if (item.repliesPolicy != null) args.putInt("repliesPolicy", item.repliesPolicy.ordinal());
Nav.go(getActivity(), ListTimelineFragment.class, args);
}

View File

@@ -14,6 +14,7 @@ public class ListTimeline extends BaseModel {
@RequiredField
public String title;
public RepliesPolicy repliesPolicy;
public boolean exclusive;
@NonNull
@Override
@@ -22,6 +23,7 @@ public class ListTimeline extends BaseModel {
"id='" + id + '\'' +
", title='" + title + '\'' +
", repliesPolicy=" + repliesPolicy +
", exclusive=" + exclusive +
'}';
}

View File

@@ -30,18 +30,20 @@ public class TimelineDefinition {
private @Nullable String listId;
private @Nullable String listTitle;
private boolean listIsExclusive;
private @Nullable String hashtagName;
public static TimelineDefinition ofList(String listId, String listTitle) {
public static TimelineDefinition ofList(String listId, String listTitle, boolean listIsExclusive) {
TimelineDefinition def = new TimelineDefinition(TimelineType.LIST);
def.listId = listId;
def.listTitle = listTitle;
def.listIsExclusive = listIsExclusive;
return def;
}
public static TimelineDefinition ofList(ListTimeline list) {
return ofList(list.id, list.title);
return ofList(list.id, list.title, list.exclusive);
}
public static TimelineDefinition ofHashtag(String hashtag) {
@@ -99,7 +101,7 @@ public class TimelineDefinition {
case LOCAL -> Icon.LOCAL;
case FEDERATED -> Icon.FEDERATED;
case POST_NOTIFICATIONS -> Icon.POST_NOTIFICATIONS;
case LIST -> Icon.LIST;
case LIST -> listIsExclusive ? Icon.EXCLUSIVE_LIST : Icon.LIST;
case HASHTAG -> Icon.HASHTAG;
case BUBBLE -> Icon.BUBBLE;
};
@@ -155,6 +157,7 @@ public class TimelineDefinition {
def.title = title;
def.listId = listId;
def.listTitle = listTitle;
def.listIsExclusive = listIsExclusive;
def.hashtagName = hashtagName;
def.icon = icon == null ? null : Icon.values()[icon.ordinal()];
return def;
@@ -164,6 +167,7 @@ public class TimelineDefinition {
if (type == TimelineType.LIST) {
args.putString("listTitle", title);
args.putString("listID", listId);
args.putBoolean("listIsExclusive", listIsExclusive);
} else if (type == TimelineType.HASHTAG) {
args.putString("hashtag", hashtagName);
}
@@ -179,6 +183,7 @@ public class TimelineDefinition {
CITY(R.drawable.ic_fluent_city_24_regular, R.string.sk_icon_city),
IMAGE(R.drawable.ic_fluent_image_24_regular, R.string.sk_icon_image),
NEWS(R.drawable.ic_fluent_news_24_regular, R.string.sk_icon_news),
FEED(R.drawable.ic_fluent_rss_24_regular, R.string.sk_icon_feed),
COLOR_PALETTE(R.drawable.ic_fluent_color_24_regular, R.string.sk_icon_color_palette),
CAT(R.drawable.ic_fluent_animal_cat_24_regular, R.string.sk_icon_cat),
DOG(R.drawable.ic_fluent_animal_dog_24_regular, R.string.sk_icon_dog),
@@ -233,6 +238,7 @@ public class TimelineDefinition {
FEDERATED(R.drawable.ic_fluent_earth_24_regular, R.string.sk_timeline_federated, true),
POST_NOTIFICATIONS(R.drawable.ic_fluent_chat_24_regular, R.string.sk_timeline_posts, true),
LIST(R.drawable.ic_fluent_people_24_regular, R.string.sk_list, true),
EXCLUSIVE_LIST(R.drawable.ic_fluent_rss_24_regular, R.string.sk_exclusive_list, true),
HASHTAG(R.drawable.ic_fluent_number_symbol_24_regular, R.string.sk_hashtag, true),
BUBBLE(R.drawable.ic_fluent_circle_24_regular, R.string.sk_timeline_bubble, true);

View File

@@ -9,6 +9,7 @@ import android.view.MenuItem;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.Switch;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -16,18 +17,20 @@ import androidx.annotation.Nullable;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.ListTimeline;
public class ListTimelineEditor extends LinearLayout {
public class ListEditor extends LinearLayout {
private ListTimeline.RepliesPolicy policy = null;
private final TextInputFrameLayout input;
private final Button button;
private final Switch exclusiveSwitch;
@SuppressLint("ClickableViewAccessibility")
public ListTimelineEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
public ListEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(R.layout.list_timeline_editor, this);
button = findViewById(R.id.button);
input = findViewById(R.id.input);
exclusiveSwitch = findViewById(R.id.exclusive_checkbox);
PopupMenu popupMenu = new PopupMenu(context, button, Gravity.CENTER_HORIZONTAL);
popupMenu.inflate(R.menu.list_reply_policies);
@@ -36,12 +39,15 @@ public class ListTimelineEditor extends LinearLayout {
button.setOnTouchListener(popupMenu.getDragToOpenListener());
button.setOnClickListener(v->popupMenu.show());
input.getEditText().setHint(context.getString(R.string.sk_list_name_hint));
findViewById(R.id.exclusive)
.setOnClickListener(v -> exclusiveSwitch.setChecked(!exclusiveSwitch.isChecked()));
setRepliesPolicy(ListTimeline.RepliesPolicy.LIST);
}
public void applyList(String title, @Nullable ListTimeline.RepliesPolicy policy) {
public void applyList(String title, boolean exclusive, @Nullable ListTimeline.RepliesPolicy policy) {
input.getEditText().setText(title);
exclusiveSwitch.setChecked(exclusive);
if (policy != null) setRepliesPolicy(policy);
}
@@ -53,6 +59,10 @@ public class ListTimelineEditor extends LinearLayout {
return policy;
}
public boolean isExclusive() {
return exclusiveSwitch.isChecked();
}
public void setRepliesPolicy(@NonNull ListTimeline.RepliesPolicy policy) {
this.policy = policy;
switch (policy) {
@@ -73,15 +83,15 @@ public class ListTimelineEditor extends LinearLayout {
return true;
}
public ListTimelineEditor(Context context, AttributeSet attrs, int defStyleAttr) {
public ListEditor(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ListTimelineEditor(Context context, AttributeSet attrs) {
public ListEditor(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ListTimelineEditor(Context context) {
public ListEditor(Context context) {
this(context, null);
}
}