fix commenting and replies to not have fake local IDs. add support for reply edit/delete.

This commit is contained in:
dosiecki 2017-07-05 05:02:11 -07:00
parent c7f0c979f0
commit 5f639515fb
20 changed files with 229 additions and 33 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -51,7 +51,7 @@
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:scaleType="fitCenter"
android:src="@drawable/reply" />
android:src="@drawable/ic_reply_gray55" />
<TextView
android:id="@+id/comment_shareddate"
@ -71,7 +71,7 @@
android:layout_marginRight="8dp"
android:layout_toLeftOf="@id/comment_shareddate"
android:scaleType="fitCenter"
android:src="@drawable/favourite" />
android:src="@drawable/ic_star_gray55" />
<TextView
android:id="@+id/comment_username"

View file

@ -7,7 +7,6 @@
android:orientation="horizontal"
android:paddingBottom="12dp"
android:paddingLeft="60dp"
android:paddingRight="10dp"
android:paddingTop="12dp" >
<View
@ -28,13 +27,24 @@
android:contentDescription="@string/description_comment_user"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/reply_edit_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentRight="true"
android:layout_marginRight="10dp"
android:layout_marginTop="3dp"
android:padding="2dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_edit_gray55" />
<TextView
android:id="@+id/reply_shareddate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="26dp"
android:layout_marginTop="5dp"
android:layout_toLeftOf="@id/reply_edit_icon"
android:layout_marginRight="10dp"
android:layout_marginTop="8dp"
style="?defaultText"
android:textSize="12sp" />

View file

@ -72,6 +72,9 @@
<string name="state_saved">SAVED</string>
<string name="reply_to">Reply to \"%s\"</string>
<string name="edit_reply">Edit reply</string>
<string name="edit_reply_update">UPDATE REPLY</string>
<string name="edit_reply_delete">DELTE REPLY</string>
<string name="alert_dialog_ok">OKAY</string>
<string name="alert_dialog_cancel">CANCEL</string>

View file

@ -380,25 +380,19 @@ public class BlurDatabaseHelper {
for (Story story : apiResponse.stories) {
for (Comment comment : story.publicComments) {
comment.storyId = story.id;
// we need a primary key for comments, so construct one
comment.id = Comment.constructId(story.id, story.feedId, comment.userId);
commentValues.add(comment.getValues());
for (Reply reply : comment.replies) {
reply.commentId = comment.id;
reply.id = reply.constructId();
replyValues.add(reply.getValues());
}
freshCommentIds.add(comment.id);
}
for (Comment comment : story.friendsComments) {
comment.storyId = story.id;
// we need a primary key for comments, so construct one
comment.id = Comment.constructId(story.id, story.feedId, comment.userId);
comment.byFriend = true;
commentValues.add(comment.getValues());
for (Reply reply : comment.replies) {
reply.commentId = comment.id;
reply.id = reply.constructId();
replyValues.add(reply.getValues());
}
freshCommentIds.add(comment.id);
@ -406,13 +400,10 @@ public class BlurDatabaseHelper {
for (Comment comment : story.friendsShares) {
comment.isPseudo = true;
comment.storyId = story.id;
// we need a primary key for comments, so construct one
comment.id = Comment.constructId(story.id, story.feedId, comment.userId);
comment.byFriend = true;
commentValues.add(comment.getValues());
for (Reply reply : comment.replies) {
reply.commentId = comment.id;
reply.id = reply.constructId();
replyValues.add(reply.getValues());
}
freshCommentIds.add(comment.id);
@ -1173,10 +1164,8 @@ public class BlurDatabaseHelper {
return comment;
}
/* TODO: we cannot locally insert comments without the ID generated by the server
public void insertUpdateComment(String storyId, String feedId, String commentText) {
// we can only insert comments as the currently logged-in user
String userId = PrefsUtils.getUserDetails(context).id;
Comment comment = new Comment();
comment.id = Comment.constructId(storyId, feedId, userId);
comment.storyId = storyId;
@ -1188,7 +1177,9 @@ public class BlurDatabaseHelper {
}
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.COMMENT_TABLE, null, comment.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
}
*/
/* TODO: we cannot locally like comments without their proper ID
public void setCommentLiked(String storyId, String userId, String feedId, boolean liked) {
String commentKey = Comment.constructId(storyId, feedId, userId);
// get a fresh copy of the story from the DB so we can append to the shared ID set
@ -1220,6 +1211,7 @@ public class BlurDatabaseHelper {
values.put(DatabaseConstants.COMMENT_LIKING_USERS, TextUtils.join(",", newIds));
synchronized (RW_MUTEX) {dbRW.update(DatabaseConstants.COMMENT_TABLE, values, DatabaseConstants.COMMENT_ID + " = ?", new String[]{commentKey});}
}
*/
public UserProfile getUserProfile(String userId) {
String[] selArgs = new String[] {userId};
@ -1242,6 +1234,7 @@ public class BlurDatabaseHelper {
return replies;
}
/* TODO: a local version of this is impossible, because the server dictates the ID.
public void replyToComment(String storyId, String feedId, String commentUserId, String replyText, long replyCreateTime) {
Reply reply = new Reply();
reply.commentId = Comment.constructId(storyId, feedId, commentUserId);
@ -1251,6 +1244,7 @@ public class BlurDatabaseHelper {
reply.id = reply.constructId();
synchronized (RW_MUTEX) {dbRW.insertWithOnConflict(DatabaseConstants.REPLY_TABLE, null, reply.getValues(), SQLiteDatabase.CONFLICT_REPLACE);}
}
*/
public void putStoryDismissed(String storyHash) {
ContentValues values = new ContentValues();

View file

@ -133,6 +133,7 @@ public class DatabaseConstants {
public static final String ACTION_TRIED = "tried";
public static final String ACTION_TYPE = "action_type";
public static final String ACTION_COMMENT_TEXT = "comment_text";
public static final String ACTION_REPLY_ID = "reply_id";
public static final String ACTION_STORY_HASH = "story_hash";
public static final String ACTION_FEED_ID = "feed_id";
public static final String ACTION_MODIFIED_FEED_IDS = "modified_feed_ids";
@ -286,6 +287,7 @@ public class DatabaseConstants {
ACTION_STORY_ID + TEXT + ", " +
ACTION_SOURCE_USER_ID + TEXT + ", " +
ACTION_COMMENT_ID + TEXT + ", " +
ACTION_REPLY_ID + TEXT + ", " +
ACTION_MODIFIED_FEED_IDS + TEXT + ", " +
ACTION_NOTIFY_FILTER + TEXT + ", " +
ACTION_NOTIFY_TYPES + TEXT +

View file

@ -12,7 +12,7 @@ import com.newsblur.database.DatabaseConstants;
public class Comment implements Serializable {
private static final long serialVersionUID = -2018705258520565390L;
// we almost always override API version with sensible PK constructed by concating story, feed, and user IDs
@SerializedName("id")
public String id;
@SerializedName("comments")
@ -74,8 +74,4 @@ public class Comment implements Serializable {
return comment;
}
public static String constructId(String storyId, String feedId, String userId) {
return TextUtils.concat(feedId, storyId, userId).toString();
}
}

View file

@ -50,8 +50,4 @@ public class Reply {
return reply;
}
// construct a string we can internally use as a PK
public String constructId() {
return TextUtils.concat(commentId, "_", Long.toString(date.getTime())).toString();
}
}

View file

@ -0,0 +1,73 @@
package com.newsblur.fragment;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.app.DialogFragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import com.newsblur.R;
import com.newsblur.domain.Story;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.PrefsUtils;
import com.newsblur.util.UIUtils;
public class EditReplyDialogFragment extends DialogFragment {
private static final String STORY = "story";
private static final String COMMENT_USER_ID = "comment_user_id";
private static final String REPLY_ID = "reply_id";
private static final String REPLY_TEXT = "reply_text";
public static EditReplyDialogFragment newInstance(Story story, String commentUserId, String replyId, String replyText) {
EditReplyDialogFragment frag = new EditReplyDialogFragment();
Bundle args = new Bundle();
args.putSerializable(STORY, story);
args.putString(COMMENT_USER_ID, commentUserId);
args.putString(REPLY_ID, replyId);
args.putString(REPLY_TEXT, replyText);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
final Story story = (Story) getArguments().getSerializable(STORY);
final String commentUserId = getArguments().getString(COMMENT_USER_ID);
final String replyId = getArguments().getString(REPLY_ID);
String replyText = getArguments().getString(REPLY_TEXT);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.edit_reply);
LayoutInflater layoutInflater = LayoutInflater.from(activity);
View replyView = layoutInflater.inflate(R.layout.reply_dialog, null);
builder.setView(replyView);
final EditText reply = (EditText) replyView.findViewById(R.id.reply_field);
reply.setText(replyText);
builder.setPositiveButton(R.string.edit_reply_update, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String replyText = reply.getText().toString();
FeedUtils.updateReply(activity, story, commentUserId, replyId, replyText);
EditReplyDialogFragment.this.dismiss();
}
});
builder.setNegativeButton(R.string.edit_reply_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
FeedUtils.deleteReply(activity, story, commentUserId, replyId);
EditReplyDialogFragment.this.dismiss();
}
});
return builder.create();
}
}

View file

@ -112,7 +112,7 @@ public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
if (comment.likingUsers != null) {
if (Arrays.asList(comment.likingUsers).contains(user.id)) {
favouriteIcon.setImageResource(R.drawable.have_favourite);
favouriteIcon.setImageResource(R.drawable.ic_star_active);
}
for (String id : comment.likingUsers) {
@ -155,7 +155,7 @@ public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
});
List<Reply> replies = FeedUtils.dbHelper.getCommentReplies(comment.id);
for (Reply reply : replies) {
for (final Reply reply : replies) {
View replyView = inflater.inflate(R.layout.include_reply, null);
TextView replyText = (TextView) replyView.findViewById(R.id.reply_text);
replyText.setText(UIUtils.fromHtml(reply.text));
@ -185,6 +185,18 @@ public class SetupCommentSectionTask extends AsyncTask<Void, Void, Void> {
replySharedDate.setText(reply.shortDate + " ago");
}
ImageView editIcon = (ImageView) replyView.findViewById(R.id.reply_edit_icon);
editIcon.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (story != null) {
DialogFragment newFragment = EditReplyDialogFragment.newInstance(story, comment.userId, reply.id, reply.text);
newFragment.show(manager, "dialog");
}
}
});
((LinearLayout) commentView.findViewById(R.id.comment_replies_container)).addView(replyView);
}

View file

@ -55,6 +55,8 @@ public class APIConstants {
public static final String PATH_LIKE_COMMENT = "/social/like_comment";
public static final String PATH_UNLIKE_COMMENT = "/social/remove_like_comment";
public static final String PATH_REPLY_TO = "/social/save_comment_reply";
public static final String PATH_EDIT_REPLY = "/social/save_comment_reply";
public static final String PATH_DELETE_REPLY = "/social/remove_comment_reply";
public static final String PATH_ADD_FEED = "/reader/add_url";
public static final String PATH_DELETE_FEED = "/reader/delete_feed";
public static final String PATH_CLASSIFIER_SAVE = "/classifier/save";
@ -85,6 +87,7 @@ public class APIConstants {
public static final String PARAMETER_FEED_SEARCH_TERM = "term";
public static final String PARAMETER_FOLDER = "folder";
public static final String PARAMETER_IN_FOLDER = "in_folder";
public static final String PARAMETER_REPLY_ID = "reply_id";
public static final String PARAMETER_COMMENT_USERID = "comment_user_id";
public static final String PARAMETER_FEEDID = "feed_id";
public static final String PARAMETER_REPLY_TEXT = "reply_comments";

View file

@ -542,6 +542,27 @@ public class APIManager {
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse editReply(String storyId, String storyFeedId, String commentUserId, String replyId, String reply) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, storyFeedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
values.put(APIConstants.PARAMETER_REPLY_ID, replyId);
values.put(APIConstants.PARAMETER_REPLY_TEXT, reply);
APIResponse response = post(buildUrl(APIConstants.PATH_EDIT_REPLY), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public NewsBlurResponse deleteReply(String storyId, String storyFeedId, String commentUserId, String replyId) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_STORYID, storyId);
values.put(APIConstants.PARAMETER_STORY_FEEDID, storyFeedId);
values.put(APIConstants.PARAMETER_COMMENT_USERID, commentUserId);
values.put(APIConstants.PARAMETER_REPLY_ID, replyId);
APIResponse response = post(buildUrl(APIConstants.PATH_DELETE_REPLY), values);
return response.getResponse(gson, NewsBlurResponse.class);
}
public boolean addFeed(String feedUrl, String folderName) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_URL, feedUrl);

View file

@ -369,6 +369,22 @@ public class FeedUtils {
triggerSync(context);
}
public static void updateReply(Context context, Story story, String commentUserId, String replyId, String replyText) {
ReadingAction ra = ReadingAction.updateReply(story.id, story.feedId, commentUserId, replyId, replyText);
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL);
triggerSync(context);
}
public static void deleteReply(Context context, Story story, String commentUserId, String replyId) {
ReadingAction ra = ReadingAction.deleteReply(story.id, story.feedId, commentUserId, replyId);
dbHelper.enqueueAction(ra);
ra.doLocal(dbHelper);
NbActivity.updateAllActivities(NbActivity.UPDATE_SOCIAL);
triggerSync(context);
}
public static void moveFeedToFolders(final Context context, final String feedId, final Set<String> toFolders, final Set<String> inFolders) {
if (toFolders.size() < 1) return;
new AsyncTask<Void, Void, NewsBlurResponse>() {

View file

@ -27,6 +27,8 @@ public class ReadingAction implements Serializable {
UNSAVE,
SHARE,
REPLY,
EDIT_REPLY,
DELETE_REPLY,
LIKE_COMMENT,
UNLIKE_COMMENT,
MUTE_FEEDS,
@ -46,6 +48,7 @@ public class ReadingAction implements Serializable {
private String sourceUserId;
private String commentReplyText; // used for both comments and replies
private String commentUserId;
private String replyId;
private String notifyFilter;
private List<String> notifyTypes;
@ -145,6 +148,27 @@ public class ReadingAction implements Serializable {
return ra;
}
public static ReadingAction updateReply(String storyId, String feedId, String commentUserId, String replyId, String commentReplyText) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.EDIT_REPLY;
ra.storyId = storyId;
ra.commentUserId = commentUserId;
ra.feedId = feedId;
ra.commentReplyText = commentReplyText;
ra.replyId = replyId;
return ra;
}
public static ReadingAction deleteReply(String storyId, String feedId, String commentUserId, String replyId) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.DELETE_REPLY;
ra.storyId = storyId;
ra.commentUserId = commentUserId;
ra.feedId = feedId;
ra.replyId = replyId;
return ra;
}
public static ReadingAction muteFeeds(Set<String> activeFeedIds, Set<String> modifiedFeedIds) {
ReadingAction ra = new ReadingAction();
ra.type = ActionType.MUTE_FEEDS;
@ -232,6 +256,21 @@ public class ReadingAction implements Serializable {
values.put(DatabaseConstants.ACTION_COMMENT_TEXT, commentReplyText);
break;
case EDIT_REPLY:
values.put(DatabaseConstants.ACTION_STORY_ID, storyId);
values.put(DatabaseConstants.ACTION_FEED_ID, feedId);
values.put(DatabaseConstants.ACTION_COMMENT_ID, commentUserId);
values.put(DatabaseConstants.ACTION_COMMENT_TEXT, commentReplyText);
values.put(DatabaseConstants.ACTION_REPLY_ID, replyId);
break;
case DELETE_REPLY:
values.put(DatabaseConstants.ACTION_STORY_ID, storyId);
values.put(DatabaseConstants.ACTION_FEED_ID, feedId);
values.put(DatabaseConstants.ACTION_COMMENT_ID, commentUserId);
values.put(DatabaseConstants.ACTION_REPLY_ID, replyId);
break;
case MUTE_FEEDS:
values.put(DatabaseConstants.ACTION_FEED_ID, DatabaseConstants.JsonHelper.toJson(activeFeedIds));
values.put(DatabaseConstants.ACTION_MODIFIED_FEED_IDS, DatabaseConstants.JsonHelper.toJson(modifiedFeedIds));
@ -300,6 +339,17 @@ public class ReadingAction implements Serializable {
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.commentUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_ID));
ra.commentReplyText = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_TEXT));
} else if (ra.type == ActionType.EDIT_REPLY) {
ra.storyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_ID));
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.commentUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_ID));
ra.commentReplyText = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_TEXT));
ra.replyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_REPLY_ID));
} else if (ra.type == ActionType.DELETE_REPLY) {
ra.storyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_STORY_ID));
ra.feedId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID));
ra.commentUserId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_COMMENT_ID));
ra.replyId = c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_REPLY_ID));
} else if (ra.type == ActionType.MUTE_FEEDS) {
ra.activeFeedIds = DatabaseConstants.JsonHelper.fromJson(c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_FEED_ID)), Set.class);
ra.modifiedFeedIds = DatabaseConstants.JsonHelper.fromJson(c.getString(c.getColumnIndexOrThrow(DatabaseConstants.ACTION_MODIFIED_FEED_IDS)), Set.class);
@ -351,6 +401,12 @@ public class ReadingAction implements Serializable {
case REPLY:
return apiManager.replyToComment(storyId, feedId, commentUserId, commentReplyText);
case EDIT_REPLY:
return apiManager.editReply(storyId, feedId, commentUserId, replyId, commentReplyText);
case DELETE_REPLY:
return apiManager.deleteReply(storyId, feedId, commentUserId, replyId);
case MUTE_FEEDS:
case UNMUTE_FEEDS:
return apiManager.saveFeedChooser(activeFeedIds);
@ -402,25 +458,39 @@ public class ReadingAction implements Serializable {
case SHARE:
dbHelper.setStoryShared(storyHash);
dbHelper.insertUpdateComment(storyId, feedId, commentReplyText);
// TODO not possible locally without server gen comment ID
//dbHelper.insertUpdateComment(storyId, feedId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL;
break;
case LIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
// TODO need to use real comment ID
// dbHelper.setCommentLiked(storyId, commentUserId, feedId, true);
impact |= NbActivity.UPDATE_SOCIAL;
break;
case UNLIKE_COMMENT:
dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
// TODO need to use real comment ID
// dbHelper.setCommentLiked(storyId, commentUserId, feedId, false);
impact |= NbActivity.UPDATE_SOCIAL;
break;
case REPLY:
dbHelper.replyToComment(storyId, feedId, commentUserId, commentReplyText, time);
// not possible locally, since the server generates the reply ID
break;
case EDIT_REPLY:
// TODO
// dbHelper.editReply(storyId, feedId, commentUserId, replyId, commentReplyText);
impact |= NbActivity.UPDATE_SOCIAL;
break;
case DELETE_REPLY:
// TODO
// dbHelper.editReply(storyId, feedId, commentUserId, replyId);
impact |= NbActivity.UPDATE_SOCIAL;
break;
case MUTE_FEEDS:
case UNMUTE_FEEDS:
dbHelper.setFeedsActive(modifiedFeedIds, type == ActionType.UNMUTE_FEEDS);