diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseEditListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseEditListFragment.java new file mode 100644 index 0000000000..5aa203f68e --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseEditListFragment.java @@ -0,0 +1,176 @@ +package org.joinmastodon.android.fragments; + +import android.app.Activity; +import android.os.Bundle; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; + +import org.joinmastodon.android.E; +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.lists.DeleteList; +import org.joinmastodon.android.api.requests.lists.GetListAccounts; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.ListDeletedEvent; +import org.joinmastodon.android.fragments.settings.BaseSettingsFragment; +import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.FollowList; +import org.joinmastodon.android.model.HeaderPaginationList; +import org.joinmastodon.android.model.viewmodel.AvatarPileListItem; +import org.joinmastodon.android.model.viewmodel.CheckableListItem; +import org.joinmastodon.android.model.viewmodel.ListItem; +import org.joinmastodon.android.ui.views.FloatingHintEditTextLayout; +import org.parceler.Parcels; + +import java.util.ArrayList; +import java.util.List; + +import androidx.recyclerview.widget.RecyclerView; +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.APIRequest; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; +import me.grishka.appkit.utils.MergeRecyclerAdapter; +import me.grishka.appkit.utils.SingleViewRecyclerAdapter; +import me.grishka.appkit.utils.V; + +public abstract class BaseEditListFragment extends BaseSettingsFragment{ + protected FollowList followList; + protected AvatarPileListItem membersItem; + protected CheckableListItem exclusiveItem; + protected FloatingHintEditTextLayout titleEditLayout; + protected EditText titleEdit; + protected Spinner showRepliesSpinner; + private APIRequest getMembersRequest; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + followList=Parcels.unwrap(getArguments().getParcelable("list")); + + membersItem=new AvatarPileListItem<>(getString(R.string.list_members), null, List.of(), 0, i->onMembersClick(), null, false); + List> items=new ArrayList<>(); + if(followList!=null){ + items.add(membersItem); + } + exclusiveItem=new CheckableListItem<>(R.string.list_exclusive, R.string.list_exclusive_subtitle, CheckableListItem.Style.SWITCH, followList!=null && followList.exclusive, this::toggleCheckableItem); + items.add(exclusiveItem); + onDataLoaded(items); + } + + @Override + public void onDestroy(){ + super.onDestroy(); + if(getMembersRequest!=null) + getMembersRequest.cancel(); + } + + @Override + protected void doLoadData(int offset, int count){} + + @Override + protected RecyclerView.Adapter getAdapter(){ + LinearLayout topView=new LinearLayout(getActivity()); + topView.setOrientation(LinearLayout.VERTICAL); + + titleEditLayout=(FloatingHintEditTextLayout) getActivity().getLayoutInflater().inflate(R.layout.floating_hint_edit_text, topView, false); + titleEdit=titleEditLayout.findViewById(R.id.edit); + titleEdit.setHint(R.string.list_name); + titleEditLayout.updateHint(); + if(followList!=null) + titleEdit.setText(followList.title); + topView.addView(titleEditLayout); + + FloatingHintEditTextLayout showRepliesLayout=(FloatingHintEditTextLayout) getActivity().getLayoutInflater().inflate(R.layout.floating_hint_spinner, topView, false); + showRepliesSpinner=showRepliesLayout.findViewById(R.id.spinner); + showRepliesLayout.setHint(R.string.list_show_replies_to); + topView.addView(showRepliesLayout); + ArrayAdapter spinnerAdapter=new ArrayAdapter<>(getActivity(), R.layout.item_spinner, List.of( + getString(R.string.list_replies_no_one), + getString(R.string.list_replies_members), + getString(R.string.list_replies_anyone) + )); + showRepliesSpinner.setAdapter(spinnerAdapter); + showRepliesSpinner.setSelection(switch(followList!=null ? followList.repliesPolicy : FollowList.RepliesPolicy.LIST){ + case FOLLOWED -> 2; + case LIST -> 1; + case NONE -> 0; + }); + ViewGroup.MarginLayoutParams llp=(ViewGroup.MarginLayoutParams)showRepliesLayout.getLabel().getLayoutParams(); + llp.setMarginStart(llp.getMarginStart()+V.dp(16)); + + MergeRecyclerAdapter adapter=new MergeRecyclerAdapter(); + adapter.addAdapter(new SingleViewRecyclerAdapter(topView)); + adapter.addAdapter(super.getAdapter()); + return adapter; + } + + @Override + protected int indexOfItemsAdapter(){ + return 1; + } + + protected void doDeleteList(){ + new DeleteList(followList.id) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(Void result){ + AccountSessionManager.get(accountID).getCacheController().deleteList(followList.id); + E.post(new ListDeletedEvent(accountID, followList.id)); + Nav.finish(BaseEditListFragment.this); + } + + @Override + public void onError(ErrorResponse error){ + Activity activity=getActivity(); + if(activity==null) + return; + error.showToast(activity); + } + }) + .wrapProgress(getActivity(), R.string.loading, true) + .exec(accountID); + } + + private void onMembersClick(){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("list", Parcels.wrap(followList)); + Nav.go(getActivity(), ListMembersFragment.class, args); + } + + protected void loadMembers(){ + getMembersRequest=new GetListAccounts(followList.id, null, 3) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(HeaderPaginationList result){ + getMembersRequest=null; + membersItem.avatars=new ArrayList<>(); + for(int i=0;i FollowList.RepliesPolicy.NONE; + case 1 -> FollowList.RepliesPolicy.LIST; + case 2 -> FollowList.RepliesPolicy.FOLLOWED; + default -> throw new IllegalStateException("Unexpected value: "+showRepliesSpinner.getSelectedItemPosition()); + }; + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/CreateListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/CreateListFragment.java new file mode 100644 index 0000000000..41be943e27 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/CreateListFragment.java @@ -0,0 +1,149 @@ +package org.joinmastodon.android.fragments; + +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.view.WindowInsets; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; + +import com.squareup.otto.Subscribe; + +import org.joinmastodon.android.E; +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.lists.CreateList; +import org.joinmastodon.android.api.requests.lists.UpdateList; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.FinishListCreationFragmentEvent; +import org.joinmastodon.android.events.ListCreatedEvent; +import org.joinmastodon.android.events.ListUpdatedEvent; +import org.joinmastodon.android.model.FollowList; +import org.joinmastodon.android.ui.utils.UiUtils; +import org.parceler.Parcels; + +import java.util.List; + +import me.grishka.appkit.Nav; +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; + +public class CreateListFragment extends BaseEditListFragment{ + private Button nextButton; + private View buttonBar; + private FollowList followList; + + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setTitle(R.string.create_list); + setSubtitle(getString(R.string.step_x_of_y, 1, 2)); + setLayout(R.layout.fragment_login); + if(savedInstanceState!=null) + followList=Parcels.unwrap(savedInstanceState.getParcelable("list")); + E.register(this); + } + + @Override + public void onDestroy(){ + super.onDestroy(); + E.unregister(this); + } + + @Override + protected int getNavigationIconDrawableResource(){ + return R.drawable.ic_baseline_close_24; + } + + @Override + public boolean wantsCustomNavigationIcon(){ + return true; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState){ + nextButton=view.findViewById(R.id.btn_next); + nextButton.setOnClickListener(this::onNextClick); + nextButton.setText(R.string.create); + buttonBar=view.findViewById(R.id.button_bar); + super.onViewCreated(view, savedInstanceState); + } + + @Override + public void onApplyWindowInsets(WindowInsets insets){ + super.onApplyWindowInsets(UiUtils.applyBottomInsetToFixedView(buttonBar, insets)); + } + + @Override + protected List getViewsForElevationEffect(){ + return List.of(getToolbar(), buttonBar); + } + + @Override + public void onSaveInstanceState(Bundle outState){ + super.onSaveInstanceState(outState); + outState.putParcelable("list", Parcels.wrap(followList)); + } + + private void onNextClick(View v){ + String title=titleEdit.getText().toString().trim(); + if(TextUtils.isEmpty(title)){ + titleEditLayout.setErrorState(getString(R.string.required_form_field_blank)); + return; + } + if(followList==null){ + new CreateList(title, getSelectedRepliesPolicy(), exclusiveItem.checked) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(FollowList result){ + followList=result; + proceed(false); + E.post(new ListCreatedEvent(accountID, result)); + AccountSessionManager.get(accountID).getCacheController().addList(result); + } + + @Override + public void onError(ErrorResponse error){ + error.showToast(getActivity()); + } + }) + .wrapProgress(getActivity(), R.string.loading, true) + .exec(accountID); + }else if(!title.equals(followList.title) || getSelectedRepliesPolicy()!=followList.repliesPolicy || exclusiveItem.checked!=followList.exclusive){ + new UpdateList(followList.id, title, getSelectedRepliesPolicy(), exclusiveItem.checked) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(FollowList result){ + followList=result; + proceed(true); + E.post(new ListUpdatedEvent(accountID, result)); + AccountSessionManager.get(accountID).getCacheController().updateList(result); + } + + @Override + public void onError(ErrorResponse error){ + error.showToast(getActivity()); + } + }) + .wrapProgress(getActivity(), R.string.loading, true) + .exec(accountID); + }else{ + proceed(true); + } + } + + private void proceed(boolean needLoadMembers){ + Bundle args=new Bundle(); + args.putString("account", accountID); + args.putParcelable("list", Parcels.wrap(followList)); + args.putBoolean("needLoadMembers", needLoadMembers); + Nav.go(getActivity(), CreateListAddMembersFragment.class, args); + getActivity().getSystemService(InputMethodManager.class).hideSoftInputFromWindow(contentView.getWindowToken(), 0); + } + + @Subscribe + public void onFinishListCreationFragment(FinishListCreationFragmentEvent ev){ + if(ev.accountID.equals(accountID) && followList!=null && ev.listID.equals(followList.id)){ + Nav.finish(this); + } + } +} diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/EditListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditListFragment.java new file mode 100644 index 0000000000..0d82441596 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/EditListFragment.java @@ -0,0 +1,67 @@ +package org.joinmastodon.android.fragments; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import org.joinmastodon.android.E; +import org.joinmastodon.android.R; +import org.joinmastodon.android.api.requests.lists.UpdateList; +import org.joinmastodon.android.api.session.AccountSessionManager; +import org.joinmastodon.android.events.ListUpdatedEvent; +import org.joinmastodon.android.model.FollowList; +import org.joinmastodon.android.ui.M3AlertDialogBuilder; + +import me.grishka.appkit.api.Callback; +import me.grishka.appkit.api.ErrorResponse; + +public class EditListFragment extends BaseEditListFragment{ + @Override + public void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + setTitle(R.string.edit_list); + loadMembers(); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ + menu.add(R.string.delete_list); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item){ + new M3AlertDialogBuilder(getActivity()) + .setTitle(R.string.delete_list) + .setMessage(getString(R.string.delete_list_confirm, followList.title)) + .setPositiveButton(R.string.delete, (dlg, which)->doDeleteList()) + .setNegativeButton(R.string.cancel, null) + .show(); + return true; + } + + @Override + public void onDestroy(){ + super.onDestroy(); + String newTitle=titleEdit.getText().toString(); + FollowList.RepliesPolicy newRepliesPolicy=getSelectedRepliesPolicy(); + boolean newExclusive=exclusiveItem.checked; + if(!newTitle.equals(followList.title) || newRepliesPolicy!=followList.repliesPolicy || newExclusive!=followList.exclusive){ + new UpdateList(followList.id, newTitle, newRepliesPolicy, newExclusive) + .setCallback(new Callback<>(){ + @Override + public void onSuccess(FollowList result){ + AccountSessionManager.get(accountID).getCacheController().updateList(result); + E.post(new ListUpdatedEvent(accountID, result)); + } + + @Override + public void onError(ErrorResponse error){ + // TODO handle errors somehow + } + }) + .exec(accountID); + } + } +}