implement adding/removing users from lists
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<>(){});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 they’re on</string>
|
||||||
|
<string name="lists_with_user">Lists with %s</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user