Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete messages by swiping and add snackbar for undoing deletion. #99

Merged
merged 13 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ public class ListMessageAdapter extends RecyclerView.Adapter<ListMessageAdapter.
.build();
}

public MessageWithImage getItem(int position) {
return items.get(position);
public List<MessageWithImage> getItems() {
return items;
}

void items(List<MessageWithImage> items) {
public void setItems(List<MessageWithImage> items) {
this.items = items;
}

Expand Down
67 changes: 59 additions & 8 deletions app/src/main/java/com/github/gotify/messages/MessagesActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@
import com.github.gotify.messages.provider.MessageFacade;
import com.github.gotify.messages.provider.MessageState;
import com.github.gotify.messages.provider.MessageWithImage;
import com.github.gotify.messages.provider.PositionPair;
import com.github.gotify.service.WebSocketService;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import com.squareup.picasso.OkHttp3Downloader;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
Expand Down Expand Up @@ -212,8 +215,8 @@ public void openDocumentation() {
startActivity(browserIntent);
}

public void delete(Message message) {
new DeleteMessage().execute(message);
public void commitDelete() {
new CommitDeleteMessage().execute();
}

protected void onUpdateApps(List<Application> applications) {
Expand Down Expand Up @@ -360,6 +363,54 @@ protected void onDestroy() {
picasso.shutdown();
}

private void scheduleDeletion(int position, Message message) {
leopoldsedev marked this conversation as resolved.
Show resolved Hide resolved
ListMessageAdapter adapter = (ListMessageAdapter) messagesView.getAdapter();

messages.deleteLocal(message);
adapter.setItems(messages.get(appId));
adapter.notifyItemRemoved(position);

showDeletionSnackbar();
}

private void undoDelete() {
PositionPair positionPair = messages.undoDeleteLocal();

if (positionPair != null) {
ListMessageAdapter adapter = (ListMessageAdapter) messagesView.getAdapter();
adapter.setItems(messages.get(appId));
int insertPosition =
appId == MessageState.ALL_MESSAGES
? positionPair.getAllPosition()
: positionPair.getAppPosition();
adapter.notifyItemInserted(insertPosition);
messagesView.smoothScrollToPosition(insertPosition);
}
}

private void showDeletionSnackbar() {
View view = swipeRefreshLayout;
Snackbar snackbar = Snackbar.make(view, R.string.snackbar_deleted, Snackbar.LENGTH_LONG);
snackbar.setAction(R.string.snackbar_undo, v -> undoDelete());
snackbar.addCallback(new SnackbarCallback());
snackbar.show();
}

private class SnackbarCallback extends BaseTransientBottomBar.BaseCallback<Snackbar> {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
super.onDismissed(transientBottomBar, event);
if (event != DISMISS_EVENT_ACTION && event != DISMISS_EVENT_CONSECUTIVE) {
// Execute deletion when the snackbar disappeared without pressing the undo button
// DISMISS_EVENT_CONSECUTIVE should be excluded as well, because it would cause the
// deletion to be sent to the server twice, since the deletion is sent to the server
// in MessageFacade if a message is deleted while another message was already
// waiting for deletion.
MessagesActivity.this.commitDelete();
}
}
}

private class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private ListMessageAdapter adapter;
private Drawable icon;
Expand Down Expand Up @@ -394,8 +445,8 @@ public boolean onMove(
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
Message message = adapter.getItem(position).message;
MessagesActivity.this.delete(message);
MessageWithImage message = adapter.getItems().get(position);
scheduleDeletion(position, message.message);
}

@Override
Expand Down Expand Up @@ -561,11 +612,11 @@ protected void onPostExecute(Void data) {
}
}

private class DeleteMessage extends AsyncTask<Message, Void, Void> {
private class CommitDeleteMessage extends AsyncTask<Void, Void, Void> {

@Override
protected Void doInBackground(Message... messages) {
MessagesActivity.this.messages.delete(first(messages));
protected Void doInBackground(Void... messages) {
MessagesActivity.this.messages.commitDelete();
return null;
}

Expand Down Expand Up @@ -648,7 +699,7 @@ private void updateMessagesAndStopLoading(List<MessageWithImage> messageWithImag
}

ListMessageAdapter adapter = (ListMessageAdapter) messagesView.getAdapter();
adapter.items(messageWithImages);
adapter.setItems(messageWithImages);
adapter.notifyDataSetChanged();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class MessageFacade {
private final MessageRequester requester;
private final MessageStateHolder state;
private final MessageImageCombiner combiner;
private Message messagePendingDeletion;

public MessageFacade(MessageApi api, ApplicationHolder applicationHolder) {
this.applicationHolder = applicationHolder;
Expand All @@ -33,6 +34,13 @@ public List<MessageWithImage> loadMore(Integer appId) {
if (state.hasNext || !state.loaded) {
PagedMessages pagedMessages = requester.loadMore(state);
this.state.newMessages(appId, pagedMessages);

// If there is a message with pending removal, it should not reappear in the list when
// reloading. Thus, it needs to be removed from the local list again after loading new
// messages.
if (messagePendingDeletion != null) {
this.state.removeMessage(messagePendingDeletion);
}
}
return get(appId);
}
Expand All @@ -52,9 +60,24 @@ public int getLastReceivedMessage() {
return state.getLastReceivedMessage();
}

public void delete(Message message) {
this.requester.asyncRemoveMessage(message);
public void deleteLocal(Message message) {
leopoldsedev marked this conversation as resolved.
Show resolved Hide resolved
this.state.removeMessage(message);
// If there is already a deletion pending, that one should be executed before scheduling the
// next deletion.
if (messagePendingDeletion != null) {
commitDelete();
}
messagePendingDeletion = message;
}

public void commitDelete() {
this.requester.asyncRemoveMessage(messagePendingDeletion);
messagePendingDeletion = null;
}

public PositionPair undoDeleteLocal() {
messagePendingDeletion = null;
return this.state.undoLastRemoveMessage();
}

public boolean deleteAll(Integer appId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class MessageStateHolder {
private int lastReceivedMessage = -1;
private Map<Integer, MessageState> states = new HashMap<>();

private Message lastRemovedMessage;
private int lastRemovedAllPosition;
leopoldsedev marked this conversation as resolved.
Show resolved Hide resolved
private int lastRemovedAppPosition;

synchronized void clear() {
states = new HashMap<>();
}
Expand All @@ -30,17 +34,7 @@ synchronized void newMessages(Integer appId, PagedMessages pagedMessages) {
}

synchronized void newMessage(Message message) {
MessageState allMessages = state(MessageState.ALL_MESSAGES);
MessageState appMessages = state(message.getAppid());

if (allMessages.loaded) {
allMessages.messages.add(0, message);
}

if (appMessages.loaded) {
appMessages.messages.add(0, message);
}

addMessage(message, 0, 0);
lastReceivedMessage = message.getId();
}

Expand Down Expand Up @@ -77,11 +71,45 @@ void removeMessage(Message message) {
MessageState appMessages = state(message.getAppid());

if (allMessages.loaded) {
allMessages.messages.remove(message);
int allPosition = allMessages.messages.indexOf(message);
allMessages.messages.remove(allPosition);
lastRemovedAllPosition = allPosition;
}

if (appMessages.loaded) {
appMessages.messages.remove(message);
int appPosition = appMessages.messages.indexOf(message);
appMessages.messages.remove(appPosition);
lastRemovedAppPosition = appPosition;
}

lastRemovedMessage = message;
}

PositionPair undoLastRemoveMessage() {
PositionPair result = null;

if (lastRemovedMessage != null) {
addMessage(lastRemovedMessage, lastRemovedAllPosition, lastRemovedAppPosition);
result = new PositionPair(lastRemovedAllPosition, lastRemovedAppPosition);

lastRemovedMessage = null;
lastRemovedAllPosition = -1;
lastRemovedAppPosition = -1;
}

return result;
}

private void addMessage(Message message, int allPosition, int appPosition) {
MessageState allMessages = state(MessageState.ALL_MESSAGES);
MessageState appMessages = state(message.getAppid());

if (allMessages.loaded && allPosition != -1) {
allMessages.messages.add(allPosition, message);
}

if (appMessages.loaded && appPosition != -1) {
appMessages.messages.add(appPosition, message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.gotify.messages.provider;

public final class PositionPair {
private final int allPosition;
private final int appPosition;

public PositionPair(int allPosition, int appPosition) {
this.allPosition = allPosition;
this.appPosition = appPosition;
}

public int getAllPosition() {
return allPosition;
}

public int getAppPosition() {
return appPosition;
}
}
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
<string name="grouped_message">Received %d messages while being disconnected</string>
<string name="delete_all">Delete all</string>
<string name="delete_logs">Delete logs</string>
<string name="snackbar_deleted">Message deleted</string>
<string name="snackbar_undo">Undo</string>
<string name="all_messages">All Messages</string>
<string name="logs">Logs</string>
<string name="message_image_desc">The image of a message</string>
Expand Down