diff --git a/app/src/main/java/com/zulip/android/activities/ExpandableStreamDrawerAdapter.java b/app/src/main/java/com/zulip/android/activities/ExpandableStreamDrawerAdapter.java index 663e285fb..070447fb0 100644 --- a/app/src/main/java/com/zulip/android/activities/ExpandableStreamDrawerAdapter.java +++ b/app/src/main/java/com/zulip/android/activities/ExpandableStreamDrawerAdapter.java @@ -7,7 +7,6 @@ import com.zulip.android.ZulipApp; import com.zulip.android.models.Message; -import com.zulip.android.models.Stream; import com.zulip.android.util.ZLog; import java.sql.SQLException; @@ -31,19 +30,22 @@ public ExpandableStreamDrawerAdapter(final Context context, Cursor cursor, int g @Override public Cursor getChildrenCursor(Cursor groupCursor) { + int pointer = zulipApp.getPointer(); List results = new ArrayList<>(); try { - results = ZulipApp.get().getDao(Message.class).queryRaw("SELECT DISTINCT subject FROM messages " + - "JOIN streams ON streams.id=messages.stream " + - "WHERE streams.id=" + groupCursor.getInt(0) + " and streams." + Stream.SUBSCRIBED_FIELD + " = " + "1 group by subject").getResults(); + results = zulipApp.getDao(Message.class).queryRaw("SELECT DISTINCT subject, " + + "count(case when messages.id > " + pointer + " and (messages." + + Message.MESSAGE_READ_FIELD + " = 0) then 1 end) as unreadcount FROM messages " + + "JOIN streams ON streams.name=messages.recipients " + + "WHERE streams.name LIKE ? group by subject", groupCursor.getString(1)).getResults(); } catch (SQLException e) { ZLog.logException(e); } - MatrixCursor matrixCursor = new MatrixCursor(new String[]{"subject", "_id"}); + MatrixCursor matrixCursor = new MatrixCursor(new String[]{"subject", "_id", UNREAD_TABLE_NAME}); for (String[] result : results) { try { - matrixCursor.addRow(new String[]{result[0], String.valueOf(groupCursor.getInt(0))}); + matrixCursor.addRow(new String[]{result[0], String.valueOf(groupCursor.getInt(0)), result[1]}); } catch (Exception e) { ZLog.logException(e); } diff --git a/app/src/main/java/com/zulip/android/activities/MessageListFragment.java b/app/src/main/java/com/zulip/android/activities/MessageListFragment.java index 4957d62b3..cda7a1468 100644 --- a/app/src/main/java/com/zulip/android/activities/MessageListFragment.java +++ b/app/src/main/java/com/zulip/android/activities/MessageListFragment.java @@ -26,7 +26,9 @@ import android.widget.TextView; import android.widget.Toast; +import com.j256.ormlite.dao.Dao; import com.j256.ormlite.stmt.QueryBuilder; +import com.j256.ormlite.stmt.UpdateBuilder; import com.j256.ormlite.stmt.Where; import com.zulip.android.R; import com.zulip.android.ZulipApp; @@ -65,6 +67,7 @@ * initiated and called by {@link ZulipActivity} */ public class MessageListFragment extends Fragment implements MessageListener { + public static final String LOG_TAG = MessageListFragment.class.getSimpleName(); private static final String PARAM_FILTER = "filter"; public NarrowFilter filter; public ZulipApp app; @@ -261,7 +264,7 @@ private void initializeNarrow() { adapter.setFooterShowing(true); } - public void onReadyToDisplay(boolean registered) { + public void onReadyToDisplay(boolean registered, boolean startup) { if (initialized && !registered) { // Already have state, and already processed any events that came in // when resuming the existing queue. @@ -270,9 +273,13 @@ public void onReadyToDisplay(boolean registered) { Log.i("onReadyToDisplay", "just a resume"); return; } - initializeNarrow(); - fetch(); + if (startup) { + // fetch 1000 messages after pointer on startup + fetch(app.getPointer(), 1000); + } else { + fetch(app.getPointer(), 100); + } initialized = true; } @@ -293,18 +300,23 @@ public void onReadyToDisplay(boolean registered, int messageId) { } initializeNarrow(); - fetch(messageId); + fetch(messageId, 100); initialized = true; } - private void showEmptyView() { Log.d("ErrorRecieving", "No Messages found for current list" + ((filter != null) ? ":" + filter.getTitle() : "")); recyclerView.setVisibility(View.GONE); emptyTextView.setVisibility(View.VISIBLE); } - private void fetch() { + /** + * Fetches all messages past current pointer and calls {@link #skipAllMessages()} + * to marks messages as read, update pointer and scroll to latest message. + * + * @param messagesFetched number of messages fetched in preceding call + */ + public void fetchAllMessages(final int messagesFetched, final CommonProgressDialog dialog) { final AsyncGetOldMessages oldMessagesReq = new AsyncGetOldMessages(this); oldMessagesReq.setCallback(new ZulipAsyncPushTask.AsyncTaskCompleteListener() { @Override @@ -312,7 +324,15 @@ public void onTaskComplete(String result, JSONObject jsonObject) { loadingMessages = false; adapter.setHeaderShowing(false); if (result.equals("0")) { - showEmptyView(); + skipAllMessages(); + dialog.dismiss(); + Toast.makeText(getContext(), R.string.bankruptcy_confirmation_msg, Toast.LENGTH_SHORT).show(); + } else { + try { + fetchAllMessages(messagesFetched + Integer.parseInt(result), dialog); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "fetchAllMessages()", e); + } } } @@ -322,8 +342,52 @@ public void onTaskFailure(String result) { adapter.setHeaderShowing(false); } }); - oldMessagesReq.execute(app.getPointer(), LoadPosition.INITIAL, 100, - 100, filter); + oldMessagesReq.execute(app.getPointer() + messagesFetched, LoadPosition.INITIAL, 0, + 200, filter); + } + + /** + * Marks all messages as read, updates pointer and scrolls to last message. + */ + private void skipAllMessages() { + // mark all messages as read + try { + markAllMessagesAsRead(); + } catch (SQLException e) { + Toast.makeText(getContext(), R.string.try_again_error_msg, Toast.LENGTH_SHORT).show(); + ZLog.logException(e); + } + + // sync pointer to latest message + int lastMessageId = app.getMaxMessageId(); + app.syncPointer(lastMessageId); + + // scroll to latest message + try { + RecyclerMessageAdapter adapter = getAdapter(); + int lastMsgIndex = adapter.getItemIndex(lastMessageId); + if (lastMsgIndex != -1) { + recyclerView.scrollToPosition(lastMsgIndex); + } else { + // when the current narrow doesn't contain the latest message + // do nothing + } + } catch (NullPointerException e) { + Toast.makeText(getContext(), R.string.try_again_error_msg, Toast.LENGTH_SHORT).show(); + ZLog.logException(e); + } + } + + /** + * Marks all messages as read after current pointer. + * @throws SQLException + */ + private void markAllMessagesAsRead() throws SQLException { + Dao messageDao = app.getDao(Message.class); + UpdateBuilder builder = messageDao.updateBuilder(); + builder.where().ge(Message.ID_FIELD, app.getPointer()); + builder.updateColumnValue(Message.MESSAGE_READ_FIELD, true); + builder.update(); } /** @@ -332,8 +396,8 @@ public void onTaskFailure(String result) { * * @param messageID anchor message id */ - private void fetch(int messageID) { - // set the achor for fetching messages as the message clicked + private void fetch(int messageID, int messages) { + // set the anchor for fetching messages as the message clicked this.anchorId = messageID; final AsyncGetOldMessages oldMessagesReq = new AsyncGetOldMessages(this); @@ -354,7 +418,7 @@ public void onTaskFailure(String result) { } }); oldMessagesReq.execute(messageID, LoadPosition.INITIAL, 100, - 100, filter); + messages, filter); } private void selectPointer() { diff --git a/app/src/main/java/com/zulip/android/activities/RecyclerMessageAdapter.java b/app/src/main/java/com/zulip/android/activities/RecyclerMessageAdapter.java index 304e98612..94a3b5f9d 100644 --- a/app/src/main/java/com/zulip/android/activities/RecyclerMessageAdapter.java +++ b/app/src/main/java/com/zulip/android/activities/RecyclerMessageAdapter.java @@ -42,7 +42,6 @@ import com.zulip.android.viewholders.MessageHeaderParent; import com.zulip.android.viewholders.MessageHolder; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -449,19 +448,8 @@ private void markThisMessageAsRead(Message message) { if (!startedFromFilter && zulipApp.getPointer() < mID) { zulipApp.syncPointer(mID); } - - boolean isMessageRead = false; - if (message.getMessageRead() != null) { - isMessageRead = message.getMessageRead(); - } - if (!isMessageRead) { - try { - updateBuilder.where().eq(Message.ID_FIELD, message.getID()); - updateBuilder.updateColumnValue(Message.MESSAGE_READ_FIELD, true); - updateBuilder.update(); - } catch (SQLException e) { - ZLog.logException(e); - } + if (!message.getMessageRead()) { + // leaving message update to "update_message_flag" event zulipApp.markMessageAsRead(message); } } catch (NullPointerException e) { diff --git a/app/src/main/java/com/zulip/android/activities/ZulipActivity.java b/app/src/main/java/com/zulip/android/activities/ZulipActivity.java index 1ea800a07..454c4c7f5 100644 --- a/app/src/main/java/com/zulip/android/activities/ZulipActivity.java +++ b/app/src/main/java/com/zulip/android/activities/ZulipActivity.java @@ -20,8 +20,10 @@ import android.database.MatrixCursor; import android.database.MergeCursor; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -671,8 +673,10 @@ private Callable getSteamCursorGenerator() { Callable steamCursorGenerator = new Callable() { @Override public Cursor call() throws Exception { - String query = "SELECT s.id as _id, s.name, s.color" - + " FROM streams as s LEFT JOIN messages as m ON s.id=m.stream "; + int pointer = app.getPointer(); + String query = "SELECT s.id as _id, s.name, s.color, count(case when m.id > " + pointer + " and (m." + Message.MESSAGE_READ_FIELD + + " = 0)"+ " then 1 end) as " + ExpandableStreamDrawerAdapter.UNREAD_TABLE_NAME + + " FROM streams as s LEFT JOIN messages as m ON s." + Stream.NAME_FIELD + " = m." + Message.RECIPIENTS_FIELD; String selectArg = null; if (!etSearchStream.getText().toString().equals("") && !etSearchStream.getText().toString().isEmpty()) { //append where clause @@ -1193,11 +1197,11 @@ public void onAnimationRepeat(Animator animator) { */ private void setupListViewAdapter() { streamsDrawerAdapter = null; - String[] groupFrom = {Stream.NAME_FIELD, Stream.COLOR_FIELD}; - int[] groupTo = {R.id.name, R.id.stream_dot}; + String[] groupFrom = {Stream.NAME_FIELD, Stream.COLOR_FIELD, ExpandableStreamDrawerAdapter.UNREAD_TABLE_NAME}; + int[] groupTo = {R.id.name, R.id.stream_dot, R.id.unread_group};; // Comparison of data elements and View - String[] childFrom = {Message.SUBJECT_FIELD}; - int[] childTo = {R.id.name_child}; + String[] childFrom = {Message.SUBJECT_FIELD, ExpandableStreamDrawerAdapter.UNREAD_TABLE_NAME}; + int[] childTo = {R.id.name_child, R.id.unread_child}; final ExpandableListView streamsDrawer = (ExpandableListView) findViewById(R.id.streams_drawer); streamsDrawer.setGroupIndicator(null); try { @@ -1257,14 +1261,6 @@ public boolean setViewValue(View view, Cursor cursor, int columnIndex) { } else { name.setTextColor(ContextCompat.getColor(ZulipActivity.this, R.color.colorTextPrimary)); } - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - resetStreamSearch(); - doNarrowToLastRead(streamName); - onNarrowFillSendBoxStream(streamName, "", false); - } - }); return true; case R.id.stream_dot: // Set the color of the (currently white) dot @@ -1274,17 +1270,30 @@ public void onClick(View v) { return true; case R.id.unread_group: TextView unreadGroupTextView = (TextView) view; + // set appropriate size for background drawable + GradientDrawable backgroundGroup = (GradientDrawable) unreadGroupTextView.getBackground(); + backgroundGroup.mutate(); final String unreadGroupCount = cursor.getString(columnIndex); -// if (unreadGroupCount.equals("0")) { -// unreadGroupTextView.setVisibility(View.GONE); -// } else { -// unreadGroupTextView.setText(unreadGroupCount); -// unreadGroupTextView.setVisibility(View.VISIBLE); -// } + int groupSize = findBubbleSize(unreadGroupCount); + backgroundGroup.setSize(groupSize, groupSize); + if (unreadGroupCount.equals("0")) { + unreadGroupTextView.setVisibility(View.GONE); + } else { + unreadGroupTextView.setText(unreadGroupCount); + unreadGroupTextView.setVisibility(View.VISIBLE); + } return true; case R.id.unread_child: TextView unreadChildTextView = (TextView) view; + // change background drawable color of child unread count to faint gray + GradientDrawable backgroundChild = (GradientDrawable) unreadChildTextView.getBackground(); + backgroundChild.mutate(); + backgroundChild.setColor(Color.LTGRAY); + + // set appropriate size for background of drawable final String unreadChildNumber = cursor.getString(columnIndex); + int childSize = findBubbleSize(unreadChildNumber); + backgroundChild.setSize(childSize, childSize); if (unreadChildNumber.equals("0")) { unreadChildTextView.setVisibility(View.GONE); } else { @@ -1306,6 +1315,28 @@ public void onClick(View v) { streamsDrawer.setAdapter(streamsDrawerAdapter); } + private int findBubbleSize(String strCounts) { + int counts = 0; + try { + counts = Integer.parseInt(strCounts); + } catch (NumberFormatException e) { + ZLog.logException(e); + return counts; + } + + int size; + if (counts / 10 == 0) { + size = (int) getResources().getDimension(R.dimen.small_bubble); + } else if (counts / 100 == 0) { + size = (int) getResources().getDimension(R.dimen.medium_bubble); + } else if (counts / 1000 == 0) { + size = (int) getResources().getDimension(R.dimen.large_bubble); + } else { + size = (int) getResources().getDimension(R.dimen.extra_large_bubble); + } + return size; + } + /** * Helper function used to call {@link ZulipActivity#onNarrow(NarrowFilter, int)} or * {@link ZulipActivity#onNarrow(NarrowFilter)} based on last message read in {@param streamName} stream. @@ -1853,7 +1884,7 @@ public void doNarrow(NarrowFilter filter) { narrowedList = MessageListFragment.newInstance(filter); // Push to the back stack if we are not already narrowed pushListFragment(narrowedList, NARROW); - narrowedList.onReadyToDisplay(true); + narrowedList.onReadyToDisplay(true, false); showView(appBarLayout); } @@ -2107,6 +2138,9 @@ public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { datePickerDialog.getDatePicker().setMaxDate(new Date().getTime()); datePickerDialog.show(); break; + case R.id.bankruptcy: + declareBankruptcy(); + break; case R.id.logout: logout(); break; @@ -2119,6 +2153,30 @@ public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { return true; } + private void declareBankruptcy() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + final CommonProgressDialog progressDialog = new CommonProgressDialog(this); + progressDialog.showWithMessage(getString(R.string.bankruptcy_progress_dialog)); + builder.setMessage(R.string.bankruptcy_alert_message) + .setTitle(R.string.bankruptcy); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // fetch all messages past current pointer + if (narrowedList != null) { + onBackPressed(); + } + homeList.fetchAllMessages(0, progressDialog); + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // do nothing + } + }); + AlertDialog dialog = builder.create(); + dialog.show(); + } + /** * Switches the current Day/Night mode to Night/Day mode * @@ -2250,10 +2308,10 @@ private void startRequests() { } - public void onReadyToDisplay(boolean registered) { - homeList.onReadyToDisplay(registered); + public void onReadyToDisplay(boolean registered, boolean startup) { + homeList.onReadyToDisplay(registered, startup); if (narrowedList != null) { - narrowedList.onReadyToDisplay(registered); + narrowedList.onReadyToDisplay(registered, startup); } } diff --git a/app/src/main/java/com/zulip/android/database/DatabaseHelper.java b/app/src/main/java/com/zulip/android/database/DatabaseHelper.java index 53c55e4e1..ded7d9594 100644 --- a/app/src/main/java/com/zulip/android/database/DatabaseHelper.java +++ b/app/src/main/java/com/zulip/android/database/DatabaseHelper.java @@ -30,7 +30,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { private static final String DATABASE_NAME = "zulip-%s.db"; // any time you make changes to your database objects, you may have to // increase the database version - private static final int DATABASE_VERSION = 8; + private static final int DATABASE_VERSION = 9; public DatabaseHelper(ZulipApp app, String email) { super(app, String.format(DATABASE_NAME, MD5Util.md5Hex(email)), null, diff --git a/app/src/main/java/com/zulip/android/models/Message.java b/app/src/main/java/com/zulip/android/models/Message.java index 559829209..bbebb326c 100644 --- a/app/src/main/java/com/zulip/android/models/Message.java +++ b/app/src/main/java/com/zulip/android/models/Message.java @@ -90,6 +90,8 @@ public class Message { private String senderShortName; @SerializedName("subject_links") private List subjectLinks; + @SerializedName("flags") + private List flags; @DatabaseField(foreign = true, columnName = SENDER_FIELD, foreignAutoRefresh = true) private Person sender; @SerializedName("type") @@ -116,7 +118,7 @@ public class Message { @DatabaseField(foreign = true, columnName = STREAM_FIELD, foreignAutoRefresh = true) private Stream stream; @DatabaseField(columnName = MESSAGE_READ_FIELD) - private Boolean messageRead; + private boolean messageRead; @DatabaseField(columnDefinition = MESSAGE_EDITED) private Boolean hasBeenEdited; //endregion @@ -243,6 +245,9 @@ public Void call() throws Exception { stream = Stream.getByName(app, m.getRecipients()); } m.setStream(stream); + if (m.getFlags() != null) { + m.setMessageRead(m.getFlags().contains(Message.MESSAGE_READ_FIELD)); + } messageDao.createOrUpdate(m); } return null; @@ -371,11 +376,11 @@ public static HTMLSchema getSchema() { return schema; } - public Boolean getMessageRead() { + public boolean getMessageRead() { return messageRead; } - public void setMessageRead(Boolean messageRead) { + public void setMessageRead(boolean messageRead) { this.messageRead = messageRead; } @@ -757,4 +762,8 @@ public String getDisplayRecipient() { return displayRecipient; } } + + public List getFlags() { + return this.flags; + } } diff --git a/app/src/main/java/com/zulip/android/models/Stream.java b/app/src/main/java/com/zulip/android/models/Stream.java index ad9337b23..2b594208f 100644 --- a/app/src/main/java/com/zulip/android/models/Stream.java +++ b/app/src/main/java/com/zulip/android/models/Stream.java @@ -175,6 +175,7 @@ public static Message getLastMessageRead(ZulipApp app, String streamName) { // query for message in given stream and orderby timestamp decreasingly return messageDao.queryBuilder().orderBy(Message.TIMESTAMP_FIELD, false) .where().eq(Message.RECIPIENTS_FIELD, new SelectArg(Message.RECIPIENTS_FIELD, streamName)) + .and().eq(Message.MESSAGE_READ_FIELD, true) .queryForFirst(); } catch (SQLException e) { ZLog.logException(e); diff --git a/app/src/main/java/com/zulip/android/networking/AsyncGetEvents.java b/app/src/main/java/com/zulip/android/networking/AsyncGetEvents.java index 8a18f77a2..7833d2eef 100644 --- a/app/src/main/java/com/zulip/android/networking/AsyncGetEvents.java +++ b/app/src/main/java/com/zulip/android/networking/AsyncGetEvents.java @@ -10,6 +10,7 @@ import com.j256.ormlite.dao.Dao; import com.j256.ormlite.dao.RuntimeExceptionDao; import com.j256.ormlite.misc.TransactionManager; +import com.j256.ormlite.stmt.UpdateBuilder; import com.zulip.android.R; import com.zulip.android.ZulipApp; import com.zulip.android.activities.LoginActivity; @@ -27,6 +28,7 @@ import com.zulip.android.networking.response.events.MutedTopicsWrapper; import com.zulip.android.networking.response.events.StreamWrapper; import com.zulip.android.networking.response.events.SubscriptionWrapper; +import com.zulip.android.networking.response.events.UpdateMessageFlagsWrapper; import com.zulip.android.networking.response.events.UpdateMessageWrapper; import com.zulip.android.util.MutedTopics; import com.zulip.android.util.TypeSwapper; @@ -220,7 +222,8 @@ public Void call() throws Exception { @Override public void run() { mActivity.getPeopleAdapter().refresh(); - mActivity.onReadyToDisplay(true); + // fetch 1000 messages on startup + mActivity.onReadyToDisplay(true, true); mActivity.checkAndSetupStreamsDrawer(); if (mActivity.commonProgressDialog != null && mActivity.commonProgressDialog.isShowing()) { mActivity.commonProgressDialog.dismiss(); @@ -290,7 +293,7 @@ public void run() { mActivity.runOnUiThread(new Runnable() { @Override public void run() { - mActivity.onReadyToDisplay(false); + mActivity.onReadyToDisplay(false, false); } }); } @@ -366,12 +369,12 @@ public Message convert(MessageWrapper messageWrapper) { processUpdateMessages(updateMessageEvents); } - // get message time limit events - List messageTimeLimit = events.getEventsOfBranchType(EventsBranch.BranchType.EDIT_MESSAGE_TIME_LIMIT); - if (!messageTimeLimit.isEmpty()) { - Log.i("AsyncGetEvents", "Received " + messageTimeLimit.size() - + " realm event"); - processMessageEditParam(messageTimeLimit); + // update message flags + List updateMessageFlags = events.getEventsOfBranchType(EventsBranch.BranchType.UPDATE_MESSAGE_FLAGS); + if (!updateMessageFlags.isEmpty()) { + Log.i("AsyncGetEvents", "Received " + updateMessageFlags.size() + + " update message flags"); + processUpdateMessageFlags(updateMessageFlags); } // get stream events @@ -382,6 +385,13 @@ public Message convert(MessageWrapper messageWrapper) { processStreams(streamEvents); } + // get message time limit events + List messageTimeLimit = events.getEventsOfBranchType(EventsBranch.BranchType.EDIT_MESSAGE_TIME_LIMIT); + if (!messageTimeLimit.isEmpty()) { + Log.i("AsyncGetEvents", "Received " + messageTimeLimit.size() + + " realm event"); + processMessageEditParam(messageTimeLimit); + } } /** @@ -456,7 +466,7 @@ private void processSubsciptions(List subscriptionWrapperList) thr mActivity.runOnUiThread(new Runnable() { @Override public void run() { - mActivity.onReadyToDisplay(true); + mActivity.onReadyToDisplay(true, false); mActivity.checkAndSetupStreamsDrawer(); } }); @@ -478,7 +488,7 @@ private void processMutedTopics(List genericMutedTopics) { mActivity.runOnUiThread(new Runnable() { @Override public void run() { - mActivity.onReadyToDisplay(true); + mActivity.onReadyToDisplay(true, false); mActivity.checkAndSetupStreamsDrawer(); } }); @@ -559,4 +569,44 @@ public void processStreams(List events) { } } } + + private void processUpdateMessageFlags(List eventList) { + for (EventsBranch event : eventList) { + UpdateMessageFlagsWrapper updateMsgFlag = (UpdateMessageFlagsWrapper) event; + switch (updateMsgFlag.getFlag()) { + case "read": + markMessagesAsRead(updateMsgFlag.getMessageIds()); + break; + default: + Log.d("AsyncEvents", "unhandled update_message_flags event"); + } + } + } + + private void markMessagesAsRead(List messageIds) { + Dao messageDao = app.getDao(Message.class); + if (messageDao == null) { + return; + } + for (Integer id : messageIds) { + if (id != null) { + try { + UpdateBuilder updateBuilder = messageDao.updateBuilder(); + updateBuilder.where().eq(Message.ID_FIELD, id).and().eq(Message.MESSAGE_READ_FIELD, false); + updateBuilder.updateColumnValue(Message.MESSAGE_READ_FIELD, true); + updateBuilder.update(); + } catch (SQLException e) { + ZLog.logException(e); + } + } + } + + // update streams and people drawers to reflect updated unread counts + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + mActivity.checkAndSetupStreamsDrawer(); + } + }); + } } diff --git a/app/src/main/java/com/zulip/android/networking/response/events/EventsBranch.java b/app/src/main/java/com/zulip/android/networking/response/events/EventsBranch.java index 3d4ac4eb9..bdd9fdc22 100644 --- a/app/src/main/java/com/zulip/android/networking/response/events/EventsBranch.java +++ b/app/src/main/java/com/zulip/android/networking/response/events/EventsBranch.java @@ -28,7 +28,8 @@ public enum BranchType { MUTED_TOPICS(MutedTopicsWrapper.class, "muted_topics"), EDIT_MESSAGE_TIME_LIMIT(EditMessageWrapper.class, "realm"), UPDATE_MESSAGE(UpdateMessageWrapper.class, "update_message"), - STREAM(StreamWrapper.class, "stream"); + STREAM(StreamWrapper.class, "stream"), + UPDATE_MESSAGE_FLAGS(UpdateMessageFlagsWrapper.class, "update_message_flags"); private final Class klazz; diff --git a/app/src/main/java/com/zulip/android/networking/response/events/UpdateMessageFlagsWrapper.java b/app/src/main/java/com/zulip/android/networking/response/events/UpdateMessageFlagsWrapper.java new file mode 100644 index 000000000..10fae6937 --- /dev/null +++ b/app/src/main/java/com/zulip/android/networking/response/events/UpdateMessageFlagsWrapper.java @@ -0,0 +1,28 @@ +package com.zulip.android.networking.response.events; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * This class is used to deserialize the update_message_flags type events {@link EventsBranch.BranchType#UPDATE_MESSAGE_FLAGS}. + * example: {"all":false,"timestamp":1488246444.8514339924,"messages":[6,7,9,10,14],"flag":"read","operation":"add","type":"update_message_flags","id":12} + * {"all":false,"timestamp":1488247612.6961550713,"messages":[55],"flag":"starred","operation":"add","type":"update_message_flags","id":3} + * {"all":false,"timestamp":1488247614.0365629196,"messages":[55],"flag":"starred","operation":"remove","type":"update_message_flags","id":4} + */ + +public class UpdateMessageFlagsWrapper extends EventsBranch { + @SerializedName("messages") + private List messageIds; + + @SerializedName("flag") + private String flag; + + public String getFlag() { + return this.flag; + } + + public List getMessageIds() { + return this.messageIds; + } +} diff --git a/app/src/main/res/drawable/unread_counts_background.xml b/app/src/main/res/drawable/unread_counts_background.xml new file mode 100644 index 000000000..aed147982 --- /dev/null +++ b/app/src/main/res/drawable/unread_counts_background.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/stream_tile_child.xml b/app/src/main/res/layout-v21/stream_tile_child.xml new file mode 100644 index 000000000..4469ccff8 --- /dev/null +++ b/app/src/main/res/layout-v21/stream_tile_child.xml @@ -0,0 +1,50 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/stream_tile_new.xml b/app/src/main/res/layout-v21/stream_tile_new.xml new file mode 100644 index 000000000..16835fd78 --- /dev/null +++ b/app/src/main/res/layout-v21/stream_tile_new.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/stream_tile_child.xml b/app/src/main/res/layout/stream_tile_child.xml index 1352ad4c7..f9f78b07e 100644 --- a/app/src/main/res/layout/stream_tile_child.xml +++ b/app/src/main/res/layout/stream_tile_child.xml @@ -16,6 +16,7 @@ android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_gravity="center_vertical" + android:layout_toLeftOf="@+id/child_container" android:layout_marginLeft="54dp" android:paddingBottom="16dp" android:paddingLeft="16dp" @@ -25,15 +26,24 @@ android:textColor="@color/colorTextSecondary" android:textSize="16sp" /> - - + android:layout_marginRight="30dp"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/stream_tile_new.xml b/app/src/main/res/layout/stream_tile_new.xml index 7c1d5ffb1..e299e7dd8 100644 --- a/app/src/main/res/layout/stream_tile_new.xml +++ b/app/src/main/res/layout/stream_tile_new.xml @@ -13,8 +13,8 @@ android:layout_marginLeft="16dp" android:background="@drawable/dot" /> - + android:layout_marginRight="30dp"> + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000..6acd04ebc --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + 18dp + 22dp + 26dp + 30dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 607007be5..9b8fda222 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -156,4 +156,8 @@ Copying message to clipboard Copying to clipboard failed Something went wrong, try again + Declare bankruptcy + Are you sure you want to skip all messages? + Skipping messages + Marked all messages as read!