From 294595513a45037359b31377aafc25ae5b58d8e7 Mon Sep 17 00:00:00 2001 From: sk Date: Tue, 10 Jan 2023 11:33:04 +0100 Subject: [PATCH] implement creating lists re: sk22#30 --- .../api/requests/lists/CreateList.java | 19 +++++ .../api/requests/lists/UpdateList.java | 14 +++ .../fragments/ListTimelinesFragment.java | 59 +++++++++---- .../android/fragments/SettingsFragment.java | 18 ++-- .../android/ui/views/ListTimelineEditor.java | 85 +++++++++++++++++++ .../ui/views/TextInputFrameLayout.java | 51 +++++++++++ .../ic_fluent_channel_add_24_regular.xml | 3 + .../main/res/layout/list_timeline_editor.xml | 44 ++++++++++ .../src/main/res/menu/list_reply_policies.xml | 6 ++ mastodon/src/main/res/menu/menu_list.xml | 8 ++ mastodon/src/main/res/values/strings_sk.xml | 7 ++ 11 files changed, 288 insertions(+), 26 deletions(-) create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/CreateList.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/UpdateList.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java create mode 100644 mastodon/src/main/java/org/joinmastodon/android/ui/views/TextInputFrameLayout.java create mode 100644 mastodon/src/main/res/drawable/ic_fluent_channel_add_24_regular.xml create mode 100644 mastodon/src/main/res/layout/list_timeline_editor.xml create mode 100644 mastodon/src/main/res/menu/list_reply_policies.xml create mode 100644 mastodon/src/main/res/menu/menu_list.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/CreateList.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/CreateList.java new file mode 100644 index 000000000..f30dde544 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/CreateList.java @@ -0,0 +1,19 @@ +package org.joinmastodon.android.api.requests.lists; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.ListTimeline; + +public class CreateList extends MastodonAPIRequest { + public CreateList(String title, ListTimeline.RepliesPolicy repliesPolicy) { + super(HttpMethod.POST, "/lists", ListTimeline.class); + Request req = new Request(); + req.title = title; + req.repliesPolicy = repliesPolicy; + setRequestBody(req); + } + + public static class Request { + public String title; + public ListTimeline.RepliesPolicy repliesPolicy; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/UpdateList.java b/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/UpdateList.java new file mode 100644 index 000000000..0a6814277 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/api/requests/lists/UpdateList.java @@ -0,0 +1,14 @@ +package org.joinmastodon.android.api.requests.lists; + +import org.joinmastodon.android.api.MastodonAPIRequest; +import org.joinmastodon.android.model.ListTimeline; + +public class UpdateList extends MastodonAPIRequest { + public UpdateList(String id, String title, ListTimeline.RepliesPolicy repliesPolicy) { + super(HttpMethod.PUT, "/lists/" + id, ListTimeline.class); + CreateList.Request req = new CreateList.Request(); + req.title = title; + req.repliesPolicy = repliesPolicy; + setRequestBody(req); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java index 8f33eee01..2cfd826bf 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelinesFragment.java @@ -1,6 +1,9 @@ package org.joinmastodon.android.fragments; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CheckBox; @@ -12,16 +15,21 @@ import androidx.recyclerview.widget.RecyclerView; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIRequest; import org.joinmastodon.android.api.requests.lists.AddAccountsToList; +import org.joinmastodon.android.api.requests.lists.CreateList; import org.joinmastodon.android.api.requests.lists.GetLists; import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList; import org.joinmastodon.android.model.ListTimeline; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.ListTimelineEditor; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.utils.BindableViewHolder; @@ -34,6 +42,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment im private HashMap userInListBefore = new HashMap<>(); private HashMap userInList = new HashMap<>(); private int inProgress = 0; + private ListsAdapter adapter; public ListTimelinesFragment() { super(10); @@ -49,7 +58,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment im profileAccountId=args.getString("profileAccount"); profileDisplayUsername=args.getString("profileDisplayUsername"); setTitle(getString(R.string.sk_lists_with_user, profileDisplayUsername)); -// setHasOptionsMenu(true); + setHasOptionsMenu(true); } } @@ -60,20 +69,38 @@ public class ListTimelinesFragment extends BaseRecyclerFragment im loadData(); } -// @Override -// public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { -// Button saveButton=new Button(getActivity()); -// saveButton.setText(R.string.save); -// saveButton.setOnClickListener(this::onSaveClick); -// LinearLayout wrap=new LinearLayout(getActivity()); -// wrap.setOrientation(LinearLayout.HORIZONTAL); -// wrap.addView(saveButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); -// wrap.setPadding(V.dp(16), V.dp(4), V.dp(16), V.dp(8)); -// wrap.setClipToPadding(false); -// MenuItem item=menu.add(R.string.save); -// item.setActionView(wrap); -// item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); -// } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_list, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.create) { + ListTimelineEditor editor = new ListTimelineEditor(getContext()); + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.sk_create_list_title) + .setView(editor) + .setPositiveButton(R.string.sk_create, (d, which) -> { + new CreateList(editor.getTitle(), editor.getRepliesPolicy()).setCallback(new Callback<>() { + @Override + public void onSuccess(ListTimeline list) { + saveListMembership(list.id, true); + data.add(0, list); + adapter.notifyItemRangeInserted(0, 1); + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(getContext()); + } + }).exec(accountId); + }) + .setNegativeButton(R.string.cancel, (d, which) -> {}) + .show(); + } + return true; + } private void saveListMembership(String listId, boolean isMember) { userInList.put(listId, isMember); @@ -119,7 +146,7 @@ public class ListTimelinesFragment extends BaseRecyclerFragment im @Override protected RecyclerView.Adapter getAdapter() { - return new ListsAdapter(); + return adapter = new ListsAdapter(); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java index 02c992283..4de83ccea 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/SettingsFragment.java @@ -53,6 +53,7 @@ import org.joinmastodon.android.model.PushSubscription; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.OutlineProviders; import org.joinmastodon.android.ui.utils.UiUtils; +import org.joinmastodon.android.ui.views.TextInputFrameLayout; import org.joinmastodon.android.updater.GithubSelfUpdater; import org.parceler.Parcels; @@ -130,17 +131,14 @@ public class SettingsFragment extends MastodonToolbarFragment{ updatePublishText(b); b.setOnClickListener(l->{ - FrameLayout inputWrap = new FrameLayout(getContext()); - EditText input = new EditText(getContext()); - input.setHint(R.string.publish); - input.setText(GlobalUserPreferences.publishButtonText.trim()); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - params.setMargins(V.dp(16), V.dp(4), V.dp(16), V.dp(16)); - input.setLayoutParams(params); - inputWrap.addView(input); - new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(inputWrap) + TextInputFrameLayout input = new TextInputFrameLayout( + getContext(), + getString(R.string.publish), + GlobalUserPreferences.publishButtonText.trim() + ); + new M3AlertDialogBuilder(getContext()).setTitle(R.string.sk_settings_publish_button_text_title).setView(input) .setPositiveButton(R.string.save, (d, which) -> { - GlobalUserPreferences.publishButtonText = input.getText().toString().trim(); + GlobalUserPreferences.publishButtonText = input.getEditText().getText().toString().trim(); GlobalUserPreferences.save(); updatePublishText(b); }) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java new file mode 100644 index 000000000..3d4f289b7 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/ListTimelineEditor.java @@ -0,0 +1,85 @@ +package org.joinmastodon.android.ui.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.PopupMenu; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.model.ListTimeline; + +public class ListTimelineEditor extends LinearLayout { + private ListTimeline.RepliesPolicy policy = null; + private TextInputFrameLayout input; + private Button button; + + @SuppressLint("ClickableViewAccessibility") + public ListTimelineEditor(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); + + PopupMenu popupMenu = new PopupMenu(context, button, Gravity.CENTER_HORIZONTAL); + popupMenu.inflate(R.menu.list_reply_policies); + popupMenu.setOnMenuItemClickListener(this::onMenuItemClick); + + button.setOnTouchListener(popupMenu.getDragToOpenListener()); + button.setOnClickListener(v->popupMenu.show()); + input.getEditText().setHint(context.getString(R.string.sk_list_name_hint)); + + setRepliesPolicy(ListTimeline.RepliesPolicy.LIST); + } + + public void applyList(ListTimeline list) { + policy = list.repliesPolicy; + input.getEditText().setText(list.title); + setRepliesPolicy(list.repliesPolicy); + } + + public String getTitle() { + return input.getEditText().getText().toString(); + } + + public ListTimeline.RepliesPolicy getRepliesPolicy() { + return policy; + } + + public void setRepliesPolicy(ListTimeline.RepliesPolicy policy) { + this.policy = policy; + switch (policy) { + case FOLLOWED -> button.setText(R.string.sk_list_replies_policy_followed); + case LIST -> button.setText(R.string.sk_list_replies_policy_list); + case NONE -> button.setText(R.string.sk_list_replies_policy_none); + } + } + + private boolean onMenuItemClick(MenuItem i) { + if (i.getItemId() == R.id.reply_policy_none) { + setRepliesPolicy(ListTimeline.RepliesPolicy.NONE); + } else if (i.getItemId() == R.id.reply_policy_followed) { + setRepliesPolicy(ListTimeline.RepliesPolicy.FOLLOWED); + } else if (i.getItemId() == R.id.reply_policy_list) { + setRepliesPolicy(ListTimeline.RepliesPolicy.LIST); + } + return true; + } + + public ListTimelineEditor(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public ListTimelineEditor(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ListTimelineEditor(Context context) { + this(context, null); + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/views/TextInputFrameLayout.java b/mastodon/src/main/java/org/joinmastodon/android/ui/views/TextInputFrameLayout.java new file mode 100644 index 000000000..4a719e0ba --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/views/TextInputFrameLayout.java @@ -0,0 +1,51 @@ +package org.joinmastodon.android.ui.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import me.grishka.appkit.utils.V; + +public class TextInputFrameLayout extends FrameLayout { + private final EditText editText; + + public TextInputFrameLayout(@NonNull Context context, CharSequence hint, CharSequence text) { + this(context, null, 0, 0, hint, text); + } + + public TextInputFrameLayout(@NonNull Context context) { + this(context, null); + } + + public TextInputFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextInputFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TextInputFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + this(context, attrs, defStyleAttr, defStyleRes, null, null); + } + + public TextInputFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes, CharSequence hint, CharSequence text) { + super(context, attrs, defStyleAttr, defStyleRes); + editText = new EditText(context); + editText.setHint(hint); + editText.setText(text); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.setMargins(V.dp(24), V.dp(4), V.dp(24), V.dp(16)); + editText.setLayoutParams(params); + addView(editText); + } + + public EditText getEditText() { + return editText; + } +} diff --git a/mastodon/src/main/res/drawable/ic_fluent_channel_add_24_regular.xml b/mastodon/src/main/res/drawable/ic_fluent_channel_add_24_regular.xml new file mode 100644 index 000000000..3825138fb --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_channel_add_24_regular.xml @@ -0,0 +1,3 @@ + + + diff --git a/mastodon/src/main/res/layout/list_timeline_editor.xml b/mastodon/src/main/res/layout/list_timeline_editor.xml new file mode 100644 index 000000000..9f1415d9f --- /dev/null +++ b/mastodon/src/main/res/layout/list_timeline_editor.xml @@ -0,0 +1,44 @@ + + + + + +