implement adding/removing users from lists

This commit is contained in:
sk
2022-11-12 16:14:45 +01:00
parent 1e0ae6e570
commit 625134605b
9 changed files with 182 additions and 8 deletions

View File

@@ -0,0 +1,17 @@
package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;
public class AddAccountsToList extends MastodonAPIRequest<Object> {
public AddAccountsToList(String listId, List<String> accountIds){
super(HttpMethod.POST, "/lists/"+listId+"/accounts", Object.class);
Request req = new Request();
req.accountIds = accountIds;
setRequestBody(req);
}
public static class Request{
public List<String> accountIds;
}
}

View File

@@ -11,4 +11,7 @@ public class GetLists extends MastodonAPIRequest<List<ListTimeline>>{
public GetLists() { public GetLists() {
super(HttpMethod.GET, "/lists", new TypeToken<>(){}); super(HttpMethod.GET, "/lists", new TypeToken<>(){});
} }
public GetLists(String accountID) {
super(HttpMethod.GET, "/accounts/"+accountID+"/lists", new TypeToken<>(){});
}
} }

View File

@@ -0,0 +1,17 @@
package org.joinmastodon.android.api.requests.lists;
import org.joinmastodon.android.api.MastodonAPIRequest;
import java.util.List;
public class RemoveAccountsFromList extends MastodonAPIRequest<Object> {
public RemoveAccountsFromList(String listId, List<String> accountIds){
super(HttpMethod.DELETE, "/lists/"+listId+"/accounts", Object.class);
Request req = new Request();
req.accountIds = accountIds;
setRequestBody(req);
}
public static class Request{
public List<String> accountIds;
}
}

View File

@@ -1,29 +1,48 @@
package org.joinmastodon.android.fragments; package org.joinmastodon.android.fragments;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.joinmastodon.android.R; 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.GetLists; import org.joinmastodon.android.api.requests.lists.GetLists;
import org.joinmastodon.android.fragments.ScrollableToTop; import org.joinmastodon.android.api.requests.lists.RemoveAccountsFromList;
import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.ListTimeline;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.api.SimpleCallback; import me.grishka.appkit.api.SimpleCallback;
import me.grishka.appkit.fragments.BaseRecyclerFragment; import me.grishka.appkit.fragments.BaseRecyclerFragment;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
import me.grishka.appkit.views.UsableRecyclerView; import me.grishka.appkit.views.UsableRecyclerView;
public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop { public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> implements ScrollableToTop {
private String accountId; private String accountId;
private String profileAccountId;
private String profileDisplayUsername;
private HashMap<String, Boolean> userInListBefore = new HashMap<>();
private HashMap<String, Boolean> userInList = new HashMap<>();
private int inProgress = 0;
public ListTimelinesFragment() { public ListTimelinesFragment() {
super(10); super(10);
@@ -32,16 +51,101 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
accountId=getArguments().getString("account"); Bundle args=getArguments();
accountId=args.getString("account");
if(args.containsKey("profileAccount")){
profileAccountId=args.getString("profileAccount");
profileDisplayUsername=args.getString("profileDisplayUsername");
setTitle(getString(R.string.lists_with_user, profileDisplayUsername));
setHasOptionsMenu(true);
}
}
@Override
protected void onShown(){
super.onShown();
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
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);
}
private void onSaveClick(View view) {
List<String> addUserToLists = new ArrayList<>();
List<String> removeUserFromLists = new ArrayList<>();
for (Map.Entry<String, Boolean> entry : userInList.entrySet()) {
Boolean changedValue = entry.getValue();
String id = entry.getKey();
if (changedValue != null && !changedValue.equals(userInListBefore.get(id))) {
(changedValue ? addUserToLists : removeUserFromLists).add(id);
}
}
List<String> accountIdList = Collections.singletonList(profileAccountId);
for (String listId : addUserToLists) {
inProgress++;
new AddAccountsToList(listId, accountIdList).setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(Object o) { onRequestComplete(); }
@Override
public void onError(ErrorResponse error) { super.onError(error); onSuccess(null); }
}).exec(accountId);
}
for (String listId : removeUserFromLists) {
inProgress++;
new RemoveAccountsFromList(listId, accountIdList).setCallback(new SimpleCallback<>(this) {
@Override
public void onSuccess(Object o) { onRequestComplete(); }
@Override
public void onError(ErrorResponse error) { super.onError(error); onSuccess(null); }
}).exec(accountId);
}
}
private void onRequestComplete() {
if (--inProgress == 0) reload();
} }
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
currentRequest=new GetLists() userInListBefore.clear();
userInList.clear();
currentRequest=(profileAccountId != null ? new GetLists(profileAccountId) : new GetLists())
.setCallback(new SimpleCallback<>(this) { .setCallback(new SimpleCallback<>(this) {
@Override @Override
public void onSuccess(List<ListTimeline> result) { public void onSuccess(List<ListTimeline> lists) {
onDataLoaded(result, false); for (ListTimeline l : lists) userInListBefore.put(l.id, true);
userInList.putAll(userInListBefore);
onDataLoaded(lists, false);
if (profileAccountId == null) return;
currentRequest=new GetLists().setCallback(new SimpleCallback<>(ListTimelinesFragment.this) {
@Override
public void onSuccess(List<ListTimeline> allLists) {
List<ListTimeline> newLists = new ArrayList<>();
for (ListTimeline l : allLists) {
if (lists.stream().noneMatch(e -> e.id.equals(l.id))) newLists.add(l);
if (!userInListBefore.containsKey(l.id)) {
userInListBefore.put(l.id, false);
}
}
userInList.putAll(userInListBefore);
onDataLoaded(newLists, false);
}
}).exec(accountId);
} }
}) })
.exec(accountId); .exec(accountId);
@@ -77,15 +181,30 @@ public class ListTimelinesFragment extends BaseRecyclerFragment<ListTimeline> im
private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{ private class ListViewHolder extends BindableViewHolder<ListTimeline> implements UsableRecyclerView.Clickable{
private final TextView title; private final TextView title;
private final CheckBox listToggle;
public ListViewHolder(){ public ListViewHolder(){
super(getActivity(), R.layout.item_list_timeline, list); super(getActivity(), R.layout.item_list_timeline, list);
title=findViewById(R.id.title); title=findViewById(R.id.title);
listToggle=findViewById(R.id.list_toggle);
} }
@Override @Override
public void onBind(ListTimeline item) { public void onBind(ListTimeline item) {
title.setText(item.title); title.setText(item.title);
if (profileAccountId != null) {
Boolean checked = userInList.get(item.id);
listToggle.setChecked(userInList.containsKey(item.id) && checked != null && checked);
listToggle.setOnClickListener(this::onClickToggle);
} else {
listToggle.setVisibility(View.GONE);
}
}
private void onClickToggle(View view) {
if (view instanceof CheckBox cb) {
userInList.put(item.id, cb.isChecked());
}
} }
@Override @Override

View File

@@ -521,10 +521,11 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
return; return;
inflater.inflate(R.menu.profile, menu); inflater.inflate(R.menu.profile, menu);
menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername())); menu.findItem(R.id.share).setTitle(getString(R.string.share_user, account.getDisplayUsername()));
menu.findItem(R.id.manage_user_lists).setTitle(getString(R.string.lists_with_user, account.getDisplayUsername()));
if(isOwnProfile){ if(isOwnProfile){
for(int i=0;i<menu.size();i++){ for(int i=0;i<menu.size();i++){
MenuItem item=menu.getItem(i); MenuItem item=menu.getItem(i);
item.setVisible(item.getItemId()==R.id.share); item.setVisible(item.getItemId()==R.id.share || item.getItemId()==R.id.manage_user_lists);
} }
return; return;
} }
@@ -580,6 +581,12 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}) })
.wrapProgress(getActivity(), R.string.loading, false) .wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID); .exec(accountID);
}else if(id==R.id.manage_user_lists){
final Bundle args=new Bundle();
args.putString("account", accountID);
args.putString("profileAccount", profileAccountID);
args.putString("profileDisplayUsername", account.getDisplayUsername());
Nav.go(getActivity(), ListTimelinesFragment.class, args);
} }
return true; return true;
} }

View File

@@ -17,12 +17,18 @@
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="@style/m3_title_medium" android:textAppearance="@style/m3_title_medium"
android:fontFamily="sans-serif" android:fontFamily="sans-serif"
android:singleLine="true" android:singleLine="true"
android:ellipsize="end" android:ellipsize="end"
tools:text="List"/> tools:text="List"/>
<CheckBox
android:id="@+id/list_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout> </LinearLayout>

View File

@@ -7,4 +7,5 @@
<item android:id="@+id/block_domain" android:title="@string/block_domain"/> <item android:id="@+id/block_domain" android:title="@string/block_domain"/>
<item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/> <item android:id="@+id/hide_boosts" android:title="@string/hide_boosts_from_user"/>
<item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/> <item android:id="@+id/open_in_browser" android:title="@string/open_in_browser"/>
<item android:id="@+id/manage_user_lists" android:title="@string/lists_with_user"/>
</menu> </menu>

View File

@@ -378,4 +378,6 @@
<string name="download_update">Download (%s)</string> <string name="download_update">Download (%s)</string>
<string name="install_update">Installieren</string> <string name="install_update">Installieren</string>
<string name="list_timelines">Listen</string> <string name="list_timelines">Listen</string>
<string name="manage_user_lists">Listen</string>
<string name="lists_with_user">Listen mit %s</string>
</resources> </resources>

View File

@@ -388,4 +388,6 @@
<string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string> <string name="privacy_policy_subtitle">Although the Mastodon app does not collect any data, the server you sign up through may have a different policy. Take a minute to review and agree to the Mastodon app privacy policy and your server\'s privacy policy.</string>
<string name="i_agree">I Agree</string> <string name="i_agree">I Agree</string>
<string name="list_timelines">Lists</string> <string name="list_timelines">Lists</string>
<string name="manage_user_lists">Lists theyre on</string>
<string name="lists_with_user">Lists with %s</string>
</resources> </resources>