Post actions

This commit is contained in:
Grishka 2022-02-18 01:50:00 +03:00
parent 371faf9bb2
commit bb463aa10a
15 changed files with 256 additions and 49 deletions

View File

@ -10,7 +10,7 @@ android {
applicationId "org.joinmastodon.android"
minSdk 23
targetSdk 31
versionCode 1
versionCode 2
versionName "0.1"
}

View File

@ -0,0 +1,10 @@
package org.joinmastodon.android.api.requests.statuses;
import org.joinmastodon.android.api.MastodonAPIRequest;
import org.joinmastodon.android.model.Status;
public class DeleteStatus extends MastodonAPIRequest<Status>{
public DeleteStatus(String id){
super(HttpMethod.DELETE, "/statuses/"+id, Status.class);
}
}

View File

@ -0,0 +1,11 @@
package org.joinmastodon.android.events;
public class StatusDeletedEvent{
public final String id;
public final String accountID;
public StatusDeletedEvent(String id, String accountID){
this.id=id;
this.accountID=accountID;
}
}

View File

@ -4,8 +4,12 @@ import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Status;
import org.parceler.Parcels;
@ -65,4 +69,16 @@ public class AccountTimelineFragment extends StatusListFragment{
if(!getArguments().getBoolean("noAutoLoad") && !loaded && !dataLoading)
loadData();
}
@Override
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
super.onStatusCountersUpdated(ev);
}
@Override
@Subscribe
public void onStatusDeleted(StatusDeletedEvent ev){
super.onStatusDeleted(ev);
}
}

View File

@ -260,6 +260,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
RecyclerView.ViewHolder holder=parent.getChildViewHolder(child);
if(holder instanceof FooterStatusDisplayItem.Holder){
float y=child.getY()+child.getHeight()-V.dp(.5f);
paint.setAlpha(Math.round(255*child.getAlpha()));
c.drawLine(child.getX(), y, child.getX()+child.getWidth(), y, paint);
}
}
@ -467,6 +468,10 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
}
}
public String getAccountID(){
return accountID;
}
@Nullable
protected <I extends StatusDisplayItem> I findItemOfType(String id, Class<I> type){
for(StatusDisplayItem item:displayItems){

View File

@ -20,7 +20,9 @@ import org.joinmastodon.android.E;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusCreatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels;
@ -107,4 +109,16 @@ public class HomeTimelineFragment extends StatusListFragment{
Toolbar toolbar=getToolbar();
toolbar.addView(logo, new Toolbar.LayoutParams(Gravity.CENTER));
}
@Override
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
super.onStatusCountersUpdated(ev);
}
@Override
@Subscribe
public void onStatusDeleted(StatusDeletedEvent ev){
super.onStatusDeleted(ev);
}
}

View File

@ -736,57 +736,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList
}
private void confirmToggleMuted(){
new M3AlertDialogBuilder(getActivity())
.setTitle(relationship.muting ? R.string.confirm_unmute_title : R.string.confirm_mute_title)
.setMessage(getString(relationship.muting ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName))
.setPositiveButton(relationship.muting ? R.string.do_unmute : R.string.do_mute, (dlg, i)->toggleMuted())
.setNegativeButton(R.string.cancel, null)
.show();
UiUtils.confirmToggleMuteUser(getActivity(), accountID, account, relationship.muting, this::updateRelationship);
}
private void confirmToggleBlocked(){
new M3AlertDialogBuilder(getActivity())
.setTitle(relationship.blocking ? R.string.confirm_unblock_title : R.string.confirm_block_title)
.setMessage(getString(relationship.blocking ? R.string.confirm_unblock : R.string.confirm_block, account.displayName))
.setPositiveButton(relationship.blocking ? R.string.do_block : R.string.do_unblock, (dlg, i)->toggleBlocked())
.setNegativeButton(R.string.cancel, null)
.show();
UiUtils.confirmToggleBlockUser(getActivity(), accountID, account, relationship.blocking, this::updateRelationship);
}
private void toggleMuted(){
new SetAccountMuted(account.id, !relationship.muting)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
relationship=result;
updateRelationship();
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
}
private void toggleBlocked(){
new SetAccountBlocked(account.id, !relationship.blocking)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
relationship=result;
updateRelationship();
}
@Override
public void onError(ErrorResponse error){
error.showToast(getActivity());
}
})
.wrapProgress(getActivity(), R.string.loading, false)
.exec(accountID);
private void updateRelationship(Relationship r){
relationship=r;
updateRelationship();
}
@Override

View File

@ -1,6 +1,10 @@
package org.joinmastodon.android.fragments;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.api.requests.trends.GetTrendingStatuses;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Status;
import java.util.List;
@ -18,4 +22,16 @@ public class SearchFragment extends StatusListFragment{
}
}).exec(accountID);
}
@Override
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
super.onStatusCountersUpdated(ev);
}
@Override
@Subscribe
public void onStatusDeleted(StatusDeletedEvent ev){
super.onStatusDeleted(ev);
}
}

View File

@ -1,14 +1,17 @@
package org.joinmastodon.android.fragments;
import android.os.Bundle;
import android.util.Log;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.E;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem;
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
@ -87,10 +90,43 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
}
}
@Subscribe
public void onStatusDeleted(StatusDeletedEvent ev){
Log.i("11", "on status deleted!");
if(!ev.accountID.equals(accountID))
return;
Status status=getStatusByID(ev.id);
if(status==null)
return;
data.remove(status);
preloadedData.remove(status);
HeaderStatusDisplayItem item=findItemOfType(ev.id, HeaderStatusDisplayItem.class);
if(item==null)
return;
int index=displayItems.indexOf(item);
int lastIndex;
for(lastIndex=index;lastIndex<displayItems.size();lastIndex++){
if(!displayItems.get(lastIndex).parentID.equals(ev.id))
break;
}
displayItems.subList(index, lastIndex).clear();
adapter.notifyItemRangeRemoved(index, lastIndex-index);
}
protected Status getContentStatusByID(String id){
Status s=getStatusByID(id);
return s==null ? null : s.getContentStatus();
}
protected Status getStatusByID(String id){
for(Status s:data){
if(s.id.equals(id)){
return s.getContentStatus();
return s;
}
}
for(Status s:preloadedData){
if(s.id.equals(id)){
return s;
}
}
return null;

View File

@ -5,8 +5,12 @@ import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.view.View;
import com.squareup.otto.Subscribe;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.statuses.GetStatusContext;
import org.joinmastodon.android.events.StatusCountersUpdatedEvent;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.StatusContext;
import org.joinmastodon.android.model.Status;
@ -81,4 +85,16 @@ public class ThreadFragment extends StatusListFragment{
if(!loaded)
footerProgress.setVisibility(View.VISIBLE);
}
@Override
@Subscribe
public void onStatusCountersUpdated(StatusCountersUpdatedEvent ev){
super.onStatusCountersUpdated(ev);
}
@Override
@Subscribe
public void onStatusDeleted(StatusDeletedEvent ev){
super.onStatusDeleted(ev);
}
}

View File

@ -18,7 +18,7 @@ public class Status extends BaseModel implements DisplayItemsParent{
public Instant createdAt;
@RequiredField
public Account account;
@RequiredField
// @RequiredField
public String content;
@RequiredField
public StatusPrivacy visibility;

View File

@ -8,13 +8,16 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Account;
@ -143,7 +146,29 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
}
private void onMoreClick(View v){
Account account=item.status.account;
PopupMenu popup=new PopupMenu(v.getContext(), v);
Menu menu=popup.getMenu();
popup.getMenuInflater().inflate(R.menu.post, menu);
if(!AccountSessionManager.getInstance().isSelf(item.parentFragment.getAccountID(), account))
menu.findItem(R.id.delete).setVisible(false);
menu.findItem(R.id.mute).setTitle(v.getResources().getString(/*relationship.muting ? R.string.unmute_user :*/ R.string.mute_user, account.displayName));
menu.findItem(R.id.block).setTitle(v.getResources().getString(/*relationship.blocking ? R.string.unblock_user :*/ R.string.block_user, account.displayName));
menu.findItem(R.id.report).setTitle(v.getResources().getString(R.string.report_user, account.displayName));
popup.setOnMenuItemClickListener(menuItem->{
int id=menuItem.getItemId();
if(id==R.id.delete){
UiUtils.confirmDeletePost(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), item.status, s->{});
}else if(id==R.id.mute){
UiUtils.confirmToggleMuteUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, false, r->{});
}else if(id==R.id.block){
UiUtils.confirmToggleBlockUser(item.parentFragment.getActivity(), item.parentFragment.getAccountID(), account, false, r->{});
}else if(id==R.id.report){
}
return true;
});
popup.show();
}
}
}

View File

@ -17,22 +17,35 @@ import android.util.Log;
import android.view.View;
import android.widget.TextView;
import org.joinmastodon.android.E;
import org.joinmastodon.android.MastodonApp;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked;
import org.joinmastodon.android.api.requests.accounts.SetAccountMuted;
import org.joinmastodon.android.api.requests.statuses.DeleteStatus;
import org.joinmastodon.android.events.StatusDeletedEvent;
import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.Relationship;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.M3AlertDialogBuilder;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import androidx.annotation.AttrRes;
import androidx.annotation.ColorRes;
import androidx.annotation.StringRes;
import androidx.browser.customtabs.CustomTabsIntent;
import me.grishka.appkit.Nav;
import me.grishka.appkit.api.Callback;
import me.grishka.appkit.api.ErrorResponse;
import me.grishka.appkit.imageloader.ViewImageLoader;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.V;
@ -172,4 +185,79 @@ public class UiUtils{
args.putString("profileAccountID", id);
Nav.go((Activity)context, ProfileFragment.class, args);
}
public static void showConfirmationAlert(Context context, @StringRes int title, @StringRes int message, @StringRes int confirmButton, Runnable onConfirmed){
showConfirmationAlert(context, context.getString(title), context.getString(message), context.getString(confirmButton), onConfirmed);
}
public static void showConfirmationAlert(Context context, CharSequence title, CharSequence message, CharSequence confirmButton, Runnable onConfirmed){
new M3AlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(confirmButton, (dlg, i)->onConfirmed.run())
.setNegativeButton(R.string.cancel, null)
.show();
}
public static void confirmToggleBlockUser(Activity activity, String accountID, Account account, boolean currentlyBlocked, Consumer<Relationship> resultCallback){
showConfirmationAlert(activity, activity.getString(currentlyBlocked ? R.string.confirm_unblock_title : R.string.confirm_block_title),
activity.getString(currentlyBlocked ? R.string.confirm_unblock : R.string.confirm_block, account.displayName),
activity.getString(currentlyBlocked ? R.string.do_block : R.string.do_unblock), ()->{
new SetAccountBlocked(account.id, !currentlyBlocked)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
}
public static void confirmToggleMuteUser(Activity activity, String accountID, Account account, boolean currentlyMuted, Consumer<Relationship> resultCallback){
showConfirmationAlert(activity, activity.getString(currentlyMuted ? R.string.confirm_unmute_title : R.string.confirm_mute_title),
activity.getString(currentlyMuted ? R.string.confirm_unmute : R.string.confirm_mute, account.displayName),
activity.getString(currentlyMuted ? R.string.do_unmute : R.string.do_mute), ()->{
new SetAccountMuted(account.id, !currentlyMuted)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Relationship result){
resultCallback.accept(result);
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.loading, false)
.exec(accountID);
});
}
public static void confirmDeletePost(Activity activity, String accountID, Status status, Consumer<Status> resultCallback){
showConfirmationAlert(activity, R.string.confirm_delete_title, R.string.confirm_delete, R.string.delete, ()->{
new DeleteStatus(status.id)
.setCallback(new Callback<>(){
@Override
public void onSuccess(Status result){
resultCallback.accept(result);
E.post(new StatusDeletedEvent(status.id, accountID));
}
@Override
public void onError(ErrorResponse error){
error.showToast(activity);
}
})
.wrapProgress(activity, R.string.deleting, false)
.exec(accountID);
});
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/delete" android:title="@string/delete"/>
<item android:id="@+id/mute" android:title="@string/mute_user"/>
<item android:id="@+id/block" android:title="@string/block_user"/>
<item android:id="@+id/report" android:title="@string/report_user"/>
</menu>

View File

@ -124,4 +124,8 @@
<string name="button_blocked">Blocked</string>
<string name="action_vote">Vote</string>
<string name="tap_to_reveal">Tap to reveal</string>
<string name="delete">Delete</string>
<string name="confirm_delete_title">Delete Post</string>
<string name="confirm_delete">Are you sure you want to delete this post?</string>
<string name="deleting">Deleting…</string>
</resources>