diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java index adf3e999e..55563033d 100644 --- a/src/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationActivity.java @@ -151,7 +151,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public static final String CHAT_ID_EXTRA = "chat_id"; public static final String IS_ARCHIVED_EXTRA = "is_archived"; public static final String TEXT_EXTRA = "draft_text"; - public static final String LAST_SEEN_EXTRA = "last_seen"; public static final String STARTING_POSITION_EXTRA = "starting_position"; private static final int PICK_GALLERY = 1; @@ -310,9 +309,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity if (isFinishing()) overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_right); quickAttachmentDrawer.onPause(); inputPanel.onPause(); - - fragment.setLastSeen(System.currentTimeMillis()); - AudioSlidePlayer.stopAll(); } @@ -1163,7 +1159,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity return; } - fragment.setLastSeen(0); + fragment.setLastSeen(-1); if (refreshFragment) { fragment.reload(recipient, chatId); diff --git a/src/org/thoughtcrime/securesms/ConversationAdapter.java b/src/org/thoughtcrime/securesms/ConversationAdapter.java index 62b8a7ffb..9752a8180 100644 --- a/src/org/thoughtcrime/securesms/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/ConversationAdapter.java @@ -91,6 +91,8 @@ public class ConversationAdapter private @NonNull DcChat dcChat; private @NonNull int[] dcMsgList = new int[0]; private int positionToPulseHighlight = -1; + private int lastSeenPosition = -1; + private long lastSeen = -1; protected static class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(final @NonNull V itemView) { @@ -120,6 +122,19 @@ public class ConversationAdapter return dcChat.getName(); } + public void setLastSeen(long timestamp) { + lastSeen = timestamp; + } + + public void updateLastSeenPosition() { + this.lastSeenPosition = findLastSeenPosition(lastSeen); + + } + + public int getLastSeenPosition() { + return lastSeenPosition; + } + @Override public int getItemCount() { return dcMsgList.length; @@ -261,24 +276,6 @@ public class ConversationAdapter } } - public int findLastSeenPosition(long lastSeen) { - /* TODO -- we shoud do this without loading all messages in the chat - if (lastSeen <= 0) return -1; - if (!isActive()) return -1; - - int count = getItemCount(); - - for (int i = 0;i return context; } + @Override + public long getHeaderId(int position) { + if (position >= getItemCount()) return -1; + if (position < 0) return -1; + + calendar.setTime(new Date(getSortTimestamp(position))); + return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); + } + + @Override + public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { + return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false)); + } + + /** + * date header view + */ + @Override + public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { + viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getSortTimestamp(position))); + } + + + public void changeData(@Nullable int[] dcMsgList) { + // should be called when there are new messages + this.dcMsgList = dcMsgList == null ? new int[0] : dcMsgList; + reloadData(); + } + + private void reloadData() { + // should be called when some items in a message are changed, eg. seen-state + recordCache.clear(); + updateLastSeenPosition(); + notifyDataSetChanged(); + } + + private int findLastSeenPosition(long lastSeen) { + if (lastSeen <= 0) return -1; + if (!isActive()) return -1; + + int count = getItemCount(); + + + for (int i = 0; i < count; i++) { + DcMsg msg = getMsg(i); + if (msg.isOutgoing() || msg.getTimestamp() <= lastSeen) { + return i - 1; + } + } + + return -1; + } + public HeaderViewHolder onCreateLastSeenViewHolder(ViewGroup parent) { return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_last_seen, parent, false)); } @@ -323,30 +373,16 @@ public class ConversationAdapter } static class LastSeenHeader extends StickyHeaderDecoration { - private final ConversationAdapter adapter; - private final long lastSeenTimestamp; - LastSeenHeader(ConversationAdapter adapter, long lastSeenTimestamp) { + LastSeenHeader(ConversationAdapter adapter) { super(adapter, false, false); this.adapter = adapter; - this.lastSeenTimestamp = lastSeenTimestamp; } @Override protected boolean hasHeader(RecyclerView parent, StickyHeaderAdapter stickyAdapter, int position) { - if (!adapter.isActive()) { - return false; - } - - if (lastSeenTimestamp <= 0) { - return false; - } - - long currentRecordTimestamp = adapter.getSortTimestamp(position); - long previousRecordTimestamp = adapter.getSortTimestamp(position + 1); - - return currentRecordTimestamp > lastSeenTimestamp && previousRecordTimestamp < lastSeenTimestamp; + return adapter.isActive() && position == adapter.getLastSeenPosition(); } @Override @@ -371,41 +407,5 @@ public class ConversationAdapter return viewHolder; } } - - - @Override - public long getHeaderId(int position) { - if (position >= getItemCount()) return -1; - if (position < 0) return -1; - - DcMsg dcMsg = getMsg(position); - - calendar.setTime(new Date(getSortTimestamp(position))); - return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); - } - - @Override - public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) { - return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.conversation_item_header, parent, false)); - } - - @Override - public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) { - DcMsg msg = getMsg(position); - viewHolder.setText(DateUtils.getRelativeDate(getContext(), locale, getSortTimestamp(position))); - } - - - public void changeData(@Nullable int[] dcMsgList) { - // should be called when there are new messages - this.dcMsgList = dcMsgList==null? new int[0] : dcMsgList; - reloadData(); - } - - public void reloadData() { - // should be called when some items in a message are changed, eg. seen-state - recordCache.clear(); - notifyDataSetChanged(); - } } diff --git a/src/org/thoughtcrime/securesms/ConversationFragment.java b/src/org/thoughtcrime/securesms/ConversationFragment.java index 44621e298..a655ca73e 100644 --- a/src/org/thoughtcrime/securesms/ConversationFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationFragment.java @@ -28,8 +28,6 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.v4.app.Fragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.view.ActionMode; @@ -63,7 +61,6 @@ import org.thoughtcrime.securesms.ConversationAdapter.HeaderViewHolder; import org.thoughtcrime.securesms.ConversationAdapter.ItemClickListener; import org.thoughtcrime.securesms.connect.ApplicationDcContext; import org.thoughtcrime.securesms.connect.DcHelper; -import org.thoughtcrime.securesms.connect.DcMsgListLoader; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.permissions.Permissions; @@ -86,328 +83,329 @@ import static org.thoughtcrime.securesms.util.RelayUtil.setForwardingMessageIds; @SuppressLint("StaticFieldLeak") public class ConversationFragment extends Fragment - implements LoaderManager.LoaderCallbacks, - DcEventCenter.DcEventDelegate + implements DcEventCenter.DcEventDelegate { - private static final String TAG = ConversationFragment.class.getSimpleName(); - private static final String KEY_LIMIT = "limit"; + private static final String TAG = ConversationFragment.class.getSimpleName(); + private static final String KEY_LIMIT = "limit"; - private static final int SCROLL_ANIMATION_THRESHOLD = 50; - private static final int CODE_ADD_EDIT_CONTACT = 77; + private static final int SCROLL_ANIMATION_THRESHOLD = 50; + private static final int CODE_ADD_EDIT_CONTACT = 77; - private final ActionModeCallback actionModeCallback = new ActionModeCallback(); - private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener(); + private final ActionModeCallback actionModeCallback = new ActionModeCallback(); + private final ItemClickListener selectionClickListener = new ConversationFragmentItemClickListener(); - private ConversationFragmentListener listener; + private ConversationFragmentListener listener; - private Recipient recipient; - private long chatId; - private long lastSeen; - private int startingPosition; - private int previousOffset; - private boolean firstLoad; - private long loaderStartTime; - private ActionMode actionMode; - private Locale locale; - private RecyclerView list; - private RecyclerView.ItemDecoration lastSeenDecoration; - private View scrollToBottomButton; - private View floatingLocationButton; - private TextView noMessageTextView; - private ApplicationDcContext dcContext; + private Recipient recipient; + private long chatId; + private int startingPosition; + private int previousOffset; + private boolean firstLoad; + private ActionMode actionMode; + private Locale locale; + private RecyclerView list; + private RecyclerView.ItemDecoration lastSeenDecoration; + private View scrollToBottomButton; + private View floatingLocationButton; + private TextView noMessageTextView; + private ApplicationDcContext dcContext; - private Debouncer markseenDebouncer; + private Debouncer markseenDebouncer; - @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); - this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); - this.dcContext = DcHelper.getContext(getContext()); + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + this.locale = (Locale) getArguments().getSerializable(PassphraseRequiredActionBarActivity.LOCALE_EXTRA); + this.dcContext = DcHelper.getContext(getContext()); - dcContext.eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this); - dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this); - dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSG_DELIVERED, this); - dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSG_FAILED, this); - dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this); - dcContext.eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this); + dcContext.eventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, this); + dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this); + dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSG_DELIVERED, this); + dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSG_FAILED, this); + dcContext.eventCenter.addObserver(DcContext.DC_EVENT_MSG_READ, this); + dcContext.eventCenter.addObserver(DcContext.DC_EVENT_CHAT_MODIFIED, this); - markseenDebouncer = new Debouncer(800); - } - - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) { - final View view = inflater.inflate(R.layout.conversation_fragment, container, false); - list = ViewUtil.findById(view, android.R.id.list); - scrollToBottomButton = ViewUtil.findById(view, R.id.scroll_to_bottom_button); - floatingLocationButton = ViewUtil.findById(view, R.id.floating_location_button); - noMessageTextView = ViewUtil.findById(view, R.id.no_messages_text_view); - - scrollToBottomButton.setOnClickListener(v -> scrollToBottom()); - - final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, true); - list.setHasFixedSize(false); - list.setLayoutManager(layoutManager); - list.setItemAnimator(null); - - // setLayerType() is needed to allow larger items (long texts in our case) - // with hardware layers, drawing may result in errors as "OpenGLRenderer: Path too large to be rendered into a texture" - list.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - - return view; - } - - @Override - public void onActivityCreated(Bundle bundle) { - super.onActivityCreated(bundle); - - initializeResources(); - initializeListAdapter(); - } - - private void setNoMessageText() { - if(chatId == DcChat.DC_CHAT_ID_DEADDROP) { - if(DcHelper.getInt(getActivity(), "show_emails")!= DcContext.DC_SHOW_EMAILS_ALL) { - noMessageTextView.setText(R.string.chat_no_contact_requests); - } - else { - noMessageTextView.setText(R.string.chat_no_messages); - } - } - else if(getListAdapter().isGroupChat()){ - if(dcContext.getChat((int) chatId).isUnpromoted()) { - noMessageTextView.setText(R.string.chat_new_group_hint); - } - else { - noMessageTextView.setText(R.string.chat_no_messages); - } - }else{ - String name = getListAdapter().getChatName(); - if(dcContext.getChat((int) chatId).isSelfTalk()) { - noMessageTextView.setText(R.string.chat_no_messages); - } - else { - String message = getString(R.string.chat_no_messages_hint, name, name); - noMessageTextView.setText(message); - } - } - } - - @Override - public void onDestroy() { - dcContext.eventCenter.removeObservers(this); - super.onDestroy(); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - this.listener = (ConversationFragmentListener)activity; - } - - @Override - public void onResume() { - super.onResume(); - - dcContext.marknoticedChat(Long.valueOf(chatId).intValue()); - if (list.getAdapter() != null) { - list.getAdapter().notifyDataSetChanged(); - } - } - - public void onNewIntent() { - if (actionMode != null) { - actionMode.finish(); + markseenDebouncer = new Debouncer(800); } - initializeResources(); - initializeListAdapter(); + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bundle) { + final View view = inflater.inflate(R.layout.conversation_fragment, container, false); + list = ViewUtil.findById(view, android.R.id.list); + scrollToBottomButton = ViewUtil.findById(view, R.id.scroll_to_bottom_button); + floatingLocationButton = ViewUtil.findById(view, R.id.floating_location_button); + noMessageTextView = ViewUtil.findById(view, R.id.no_messages_text_view); - if (chatId == -1) { - reloadList(); - updateLocationButton(); - } - } + scrollToBottomButton.setOnClickListener(v -> scrollToBottom()); - public void moveToLastSeen() { - if (lastSeen <= 0) { - return; + final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, true); + list.setHasFixedSize(false); + list.setLayoutManager(layoutManager); + list.setItemAnimator(null); + + // setLayerType() is needed to allow larger items (long texts in our case) + // with hardware layers, drawing may result in errors as "OpenGLRenderer: Path too large to be rendered into a texture" + list.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + + return view; } - if (list == null || getListAdapter() == null) { - Log.w(TAG, "Tried to move to last seen position, but we hadn't initialized the view yet."); - return; + @Override + public void onActivityCreated(Bundle bundle) { + super.onActivityCreated(bundle); + initializeResources(); + initializeListAdapter(); } - int position = getListAdapter().findLastSeenPosition(lastSeen); - scrollToLastSeenPosition(position); - } - - private void initializeResources() { - this.chatId = this.getActivity().getIntent().getIntExtra(ConversationActivity.CHAT_ID_EXTRA, -1); - this.recipient = Recipient.from(getActivity(), Address.fromChat((int)this.chatId)); - this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1); - this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); - this.firstLoad = true; - - OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); - list.addOnScrollListener(scrollListener); - } - - private void initializeListAdapter() { - if (this.recipient != null && this.chatId != -1) { - ConversationAdapter adapter = new ConversationAdapter(getActivity(), this.recipient.getChat(), GlideApp.with(this), locale, selectionClickListener, this.recipient); - list.setAdapter(adapter); - list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); - - setLastSeen(lastSeen); - reloadList(); - updateLocationButton(); - } - } - - private void setCorrectMenuVisibility(Menu menu) { - Set messageRecords = getListAdapter().getSelectedItems(); - - if (actionMode != null && messageRecords.size() == 0) { - actionMode.finish(); - return; + private void setNoMessageText() { + if(chatId == DcChat.DC_CHAT_ID_DEADDROP) { + if(DcHelper.getInt(getActivity(), "show_emails")!= DcContext.DC_SHOW_EMAILS_ALL) { + noMessageTextView.setText(R.string.chat_no_contact_requests); + } + else { + noMessageTextView.setText(R.string.chat_no_messages); + } + } + else if(getListAdapter().isGroupChat()){ + if(dcContext.getChat((int) chatId).isUnpromoted()) { + noMessageTextView.setText(R.string.chat_new_group_hint); + } + else { + noMessageTextView.setText(R.string.chat_no_messages); + } + }else{ + String name = getListAdapter().getChatName(); + if(dcContext.getChat((int) chatId).isSelfTalk()) { + noMessageTextView.setText(R.string.chat_no_messages); + } + else { + String message = getString(R.string.chat_no_messages_hint, name, name); + noMessageTextView.setText(message); + } + } } - if (messageRecords.size() > 1) { - menu.findItem(R.id.menu_context_details).setVisible(false); - menu.findItem(R.id.menu_context_save_attachment).setVisible(false); - } else { - DcMsg messageRecord = messageRecords.iterator().next(); - menu.findItem(R.id.menu_context_details).setVisible(true); - menu.findItem(R.id.menu_context_save_attachment).setVisible(messageRecord.hasFile()); - } - } - - private ConversationAdapter getListAdapter() { - return (ConversationAdapter) list.getAdapter(); - } - - private DcMsg getSelectedMessageRecord() { - Set messageRecords = getListAdapter().getSelectedItems(); - - if (messageRecords.size() == 1) return messageRecords.iterator().next(); - else throw new AssertionError(); - } - - public void reload(Recipient recipient, long chatId) { - this.recipient = recipient; - - if (this.chatId != chatId) { - this.chatId = chatId; - initializeListAdapter(); - } - } - - public void scrollToBottom() { - if (((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition() < SCROLL_ANIMATION_THRESHOLD) { - list.smoothScrollToPosition(0); - } else { - list.scrollToPosition(0); - } - } - - public void setLastSeen(long lastSeen) { - this.lastSeen = lastSeen; - if (lastSeenDecoration != null) { - list.removeItemDecoration(lastSeenDecoration); + @Override + public void onDestroy() { + dcContext.eventCenter.removeObservers(this); + super.onDestroy(); } - lastSeenDecoration = new ConversationAdapter.LastSeenHeader(getListAdapter(), lastSeen); - list.addItemDecoration(lastSeenDecoration); - } - - private String getMessageContent(DcMsg msg, DcMsg prev_msg) - { - String ret = ""; - - if (msg.getFromId() != prev_msg.getFromId()) { - DcContact contact = dcContext.getContact(msg.getFromId()); - ret += contact.getDisplayName() + ":\n"; + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + this.listener = (ConversationFragmentListener)activity; } - if(msg.getType() == DcMsg.DC_MSG_TEXT) { - ret += msg.getText(); - } - else { - ret += msg.getSummarytext(1000); + @Override + public void onResume() { + super.onResume(); + + dcContext.marknoticedChat((int) chatId); + if (list.getAdapter() != null) { + list.getAdapter().notifyDataSetChanged(); + } } - return ret; - } - private void handleCopyMessage(final Set dcMsgsSet) { - List dcMsgsList = new LinkedList<>(dcMsgsSet); - Collections.sort(dcMsgsList, new Comparator() { - @Override - public int compare(DcMsg lhs, DcMsg rhs) { - if (lhs.getDateReceived() < rhs.getDateReceived()) return -1; - else if (lhs.getDateReceived() == rhs.getDateReceived()) return 0; - else return 1; - } - }); - - StringBuilder result = new StringBuilder(); - - DcMsg prevMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); - for (DcMsg msg : dcMsgsList) { - if (result.length()>0) { - result.append("\n\n"); - } - result.append(getMessageContent(msg, prevMsg)); - prevMsg = msg; + @Override + public void onPause() { + super.onPause(); + setLastSeen(System.currentTimeMillis()); } - if (result.length()>0) { - try { - ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); - clipboard.setText(result.toString()); - Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.copied_to_clipboard), Toast.LENGTH_LONG).show(); - } - catch(Exception e) { - e.printStackTrace(); - } + public void onNewIntent() { + if (actionMode != null) { + actionMode.finish(); + } + + initializeResources(); + initializeListAdapter(); + + if (chatId == -1) { + reloadList(); + updateLocationButton(); + } } - } - private void handleDeleteMessages(final Set messageRecords) { - int messagesCount = messageRecords.size(); + public void moveToLastSeen() { + if (list == null || getListAdapter() == null) { + Log.w(TAG, "Tried to move to last seen position, but we hadn't initialized the view yet."); + return; + } - new AlertDialog.Builder(getActivity()) - .setMessage(getActivity().getResources().getQuantityString(R.plurals.ask_delete_messages, messagesCount, messagesCount)) - .setCancelable(true) - .setPositiveButton(R.string.delete, (dialog, which) -> { - int[] ids = DcMsg.msgSetToIds(messageRecords); - dcContext.deleteMsgs(ids); - actionMode.finish(); - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); - } + if (getListAdapter().getLastSeenPosition() < 0) { + return; + } + scrollToLastSeenPosition(getListAdapter().getLastSeenPosition()); + } - private void handleDisplayDetails(DcMsg dcMsg) { - String info_str = dcContext.getMsgInfo(dcMsg.getId()); - new AlertDialog.Builder(getActivity()) - .setMessage(info_str) - .setPositiveButton(android.R.string.ok, null) - .show(); - } + private void initializeResources() { + this.chatId = this.getActivity().getIntent().getIntExtra(ConversationActivity.CHAT_ID_EXTRA, -1); + this.recipient = Recipient.from(getActivity(), Address.fromChat((int)this.chatId)); + this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1); + this.firstLoad = true; - private void handleForwardMessage(final Set messageRecords) { - Intent composeIntent = new Intent(getActivity(), ConversationListActivity.class); - int[] msgIds = DcMsg.msgSetToIds(messageRecords); - setForwardingMessageIds(composeIntent, msgIds); - startActivityForResult(composeIntent, REQUEST_RELAY); - getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); - } + OnScrollListener scrollListener = new ConversationScrollListener(getActivity()); + list.addOnScrollListener(scrollListener); + } - private void handleResendMessage(final DcMsg message) { - // TODO + private void initializeListAdapter() { + if (this.recipient != null && this.chatId != -1) { + ConversationAdapter adapter = new ConversationAdapter(getActivity(), this.recipient.getChat(), GlideApp.with(this), locale, selectionClickListener, this.recipient); + list.setAdapter(adapter); + list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false)); + + reloadList(); + updateLocationButton(); + } + } + + private void setCorrectMenuVisibility(Menu menu) { + Set messageRecords = getListAdapter().getSelectedItems(); + + if (actionMode != null && messageRecords.size() == 0) { + actionMode.finish(); + return; + } + + if (messageRecords.size() > 1) { + menu.findItem(R.id.menu_context_details).setVisible(false); + menu.findItem(R.id.menu_context_save_attachment).setVisible(false); + } else { + DcMsg messageRecord = messageRecords.iterator().next(); + menu.findItem(R.id.menu_context_details).setVisible(true); + menu.findItem(R.id.menu_context_save_attachment).setVisible(messageRecord.hasFile()); + } + } + + private ConversationAdapter getListAdapter() { + return (ConversationAdapter) list.getAdapter(); + } + + private DcMsg getSelectedMessageRecord() { + Set messageRecords = getListAdapter().getSelectedItems(); + + if (messageRecords.size() == 1) return messageRecords.iterator().next(); + else throw new AssertionError(); + } + + public void reload(Recipient recipient, long chatId) { + this.recipient = recipient; + + if (this.chatId != chatId) { + this.chatId = chatId; + initializeListAdapter(); + } + } + + public void scrollToBottom() { + if (((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition() < SCROLL_ANIMATION_THRESHOLD) { + list.smoothScrollToPosition(0); + } else { + list.scrollToPosition(0); + } + } + + public void setLastSeen(long lastSeen) { + getListAdapter().setLastSeen(lastSeen); + if (lastSeenDecoration != null) { + list.removeItemDecoration(lastSeenDecoration); + } + + if (lastSeen > 0) { + lastSeenDecoration = new ConversationAdapter.LastSeenHeader(getListAdapter()/*, lastSeen*/); + list.addItemDecoration(lastSeenDecoration); + } + } + + private String getMessageContent(DcMsg msg, DcMsg prev_msg) + { + String ret = ""; + + if (msg.getFromId() != prev_msg.getFromId()) { + DcContact contact = dcContext.getContact(msg.getFromId()); + ret += contact.getDisplayName() + ":\n"; + } + + if(msg.getType() == DcMsg.DC_MSG_TEXT) { + ret += msg.getText(); + } + else { + ret += msg.getSummarytext(1000); + } + + return ret; + } + + private void handleCopyMessage(final Set dcMsgsSet) { + List dcMsgsList = new LinkedList<>(dcMsgsSet); + Collections.sort(dcMsgsList, new Comparator() { + @Override + public int compare(DcMsg lhs, DcMsg rhs) { + if (lhs.getDateReceived() < rhs.getDateReceived()) return -1; + else if (lhs.getDateReceived() == rhs.getDateReceived()) return 0; + else return 1; + } + }); + + StringBuilder result = new StringBuilder(); + + DcMsg prevMsg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); + for (DcMsg msg : dcMsgsList) { + if (result.length()>0) { + result.append("\n\n"); + } + result.append(getMessageContent(msg, prevMsg)); + prevMsg = msg; + } + + if (result.length()>0) { + try { + ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + clipboard.setText(result.toString()); + Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.copied_to_clipboard), Toast.LENGTH_LONG).show(); + } + catch(Exception e) { + e.printStackTrace(); + } + } + } + + private void handleDeleteMessages(final Set messageRecords) { + int messagesCount = messageRecords.size(); + + new AlertDialog.Builder(getActivity()) + .setMessage(getActivity().getResources().getQuantityString(R.plurals.ask_delete_messages, messagesCount, messagesCount)) + .setCancelable(true) + .setPositiveButton(R.string.delete, (dialog, which) -> { + int[] ids = DcMsg.msgSetToIds(messageRecords); + dcContext.deleteMsgs(ids); + actionMode.finish(); + }) + .setNegativeButton(android.R.string.cancel, null) + .show(); + } + + private void handleDisplayDetails(DcMsg dcMsg) { + String info_str = dcContext.getMsgInfo(dcMsg.getId()); + new AlertDialog.Builder(getActivity()) + .setMessage(info_str) + .setPositiveButton(android.R.string.ok, null) + .show(); + } + + private void handleForwardMessage(final Set messageRecords) { + Intent composeIntent = new Intent(getActivity(), ConversationListActivity.class); + int[] msgIds = DcMsg.msgSetToIds(messageRecords); + setForwardingMessageIds(composeIntent, msgIds); + startActivityForResult(composeIntent, REQUEST_RELAY); + getActivity().overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out); + } + + private void handleResendMessage(final DcMsg message) { + // TODO /* final Context context = getActivity().getApplicationContext(); new AsyncTask() { @@ -418,448 +416,402 @@ public class ConversationFragment extends Fragment } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, message); */ - } - - private void handleReplyMessage(final DcMsg message) { - listener.handleReplyMessage(message); - } - - private void handleSaveAttachment(final DcMsg message) { - SaveAttachmentTask.showWarningDialog(getContext(), (dialogInterface, i) -> { - Permissions.with(this) - .request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) - .ifNecessary() - .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied)) - .onAnyDenied(() -> Toast.makeText(getContext(), R.string.perm_explain_access_to_storage_denied, Toast.LENGTH_LONG).show()) - .onAllGranted(() -> { - SaveAttachmentTask saveTask = new SaveAttachmentTask(getContext()); - SaveAttachmentTask.Attachment attachment = new SaveAttachmentTask.Attachment( - Uri.fromFile(message.getFileAsFile()), message.getFilemime(), message.getDateReceived(), message.getFilename()); - saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachment); - actionMode.finish(); - }) - .execute(); - }); - } - - private void reloadList() { - ConversationAdapter adapter = getListAdapter(); - if (adapter == null) { - return; } - // just for testing, here are two variants. - final boolean loadSynchronous = true; - if (loadSynchronous) { - // this typically takes <1 ms ... - loaderStartTime = System.currentTimeMillis(); - int[] msgs = DcHelper.getContext(getContext()).getChatMsgs((int) chatId, 0, 0); - onLoadFinished(null, msgs); - } - //FIXME: remove dead code - else { - // ... while this takes >100 ms - LoaderManager loaderManager = getLoaderManager(); - if (loaderManager != null) { - loaderManager.restartLoader(0, Bundle.EMPTY, this); - } - } - } - - private void updateLocationButton() { - floatingLocationButton.setVisibility(dcContext.isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - Log.w(TAG, "onCreateLoader"); - loaderStartTime = System.currentTimeMillis(); - - return new DcMsgListLoader(getActivity(), (int) chatId, 0, 0); - } - - @Override - public void onLoadFinished(Loader arg0, int[] dcMsgList) { - long loadTime = System.currentTimeMillis() - loaderStartTime; - int count = dcMsgList.length; - Log.w(TAG, "onLoadFinished - took " + loadTime + " ms to load a message list of size " + count); - - ConversationAdapter adapter = getListAdapter(); - if (adapter == null) { - return; + private void handleReplyMessage(final DcMsg message) { + listener.handleReplyMessage(message); } - if (lastSeen == -1) { - //setLastSeen(loader.getLastSeen()); -- TODO + private void handleSaveAttachment(final DcMsg message) { + SaveAttachmentTask.showWarningDialog(getContext(), (dialogInterface, i) -> { + Permissions.with(this) + .request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) + .ifNecessary() + .withPermanentDenialDialog(getString(R.string.perm_explain_access_to_storage_denied)) + .onAnyDenied(() -> Toast.makeText(getContext(), R.string.perm_explain_access_to_storage_denied, Toast.LENGTH_LONG).show()) + .onAllGranted(() -> { + SaveAttachmentTask saveTask = new SaveAttachmentTask(getContext()); + SaveAttachmentTask.Attachment attachment = new SaveAttachmentTask.Attachment( + Uri.fromFile(message.getFileAsFile()), message.getFilemime(), message.getDateReceived(), message.getFilename()); + saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachment); + actionMode.finish(); + }) + .execute(); + }); } - adapter.changeData(dcMsgList); + //TODO: clarify scroll behavior + private void reloadList() { + ConversationAdapter adapter = getListAdapter(); + if (adapter == null) { + return; + } + int[] msgs = DcHelper.getContext(getContext()).getChatMsgs((int) chatId, 0, 0); + adapter.changeData(msgs); + int lastSeenPosition = adapter.getLastSeenPosition(); - int lastSeenPosition = adapter.findLastSeenPosition(lastSeen); + if (firstLoad) { + if (startingPosition >= 0) { + scrollToStartingPosition(startingPosition); + } else { + scrollToLastSeenPosition(lastSeenPosition); + } + firstLoad = false; + } else if (previousOffset > 0) { + int count = msgs.length; + int scrollPosition = previousOffset + ((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition(); + scrollPosition = Math.min(scrollPosition, count - 1); - if (firstLoad) { - if (startingPosition >= 0) { - scrollToStartingPosition(startingPosition); - } else { - scrollToLastSeenPosition(lastSeenPosition); - } - firstLoad = false; - } else if (previousOffset > 0) { - int scrollPosition = previousOffset + ((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition(); - scrollPosition = Math.min(scrollPosition, count - 1); + View firstView = list.getLayoutManager().getChildAt(scrollPosition); + int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom()); - View firstView = list.getLayoutManager().getChildAt(scrollPosition); - int pixelOffset = (firstView == null) ? 0 : (firstView.getBottom() - list.getPaddingBottom()); + ((LinearLayoutManager) list.getLayoutManager()).scrollToPositionWithOffset(scrollPosition, pixelOffset); + previousOffset = 0; + } - ((LinearLayoutManager) list.getLayoutManager()).scrollToPositionWithOffset(scrollPosition, pixelOffset); - previousOffset = 0; + if(!adapter.isActive()){ + setNoMessageText(); + noMessageTextView.setVisibility(View.VISIBLE); + } + else{ + noMessageTextView.setVisibility(View.GONE); + } } - if(!adapter.isActive()){ - setNoMessageText(); - noMessageTextView.setVisibility(View.VISIBLE); - } - else{ - noMessageTextView.setVisibility(View.GONE); + private void updateLocationButton() { + floatingLocationButton.setVisibility(dcContext.isSendingLocationsToChat((int) chatId)? View.VISIBLE : View.GONE); } - if (lastSeenPosition <= 0) { - setLastSeen(0); - } - } - - @Override - public void onLoaderReset(Loader arg0) { - if (list.getAdapter() != null) { - getListAdapter().changeData(null); - } - } - - private void scrollToStartingPosition(final int startingPosition) { - list.post(() -> { - list.getLayoutManager().scrollToPosition(startingPosition); - getListAdapter().pulseHighlightItem(startingPosition); - }); - } - - private void scrollToLastSeenPosition(final int lastSeenPosition) { - if (lastSeenPosition > 0) { - list.post(() -> ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight())); - } - } - - public interface ConversationFragmentListener { - void setChatId(int threadId); - void handleReplyMessage(DcMsg messageRecord); - } - - private class ConversationScrollListener extends OnScrollListener { - - private final Animation scrollButtonInAnimation; - private final Animation scrollButtonOutAnimation; - - private boolean wasAtBottom = true; - private boolean wasAtZoomScrollHeight = false; - private long lastPositionId = -1; - - ConversationScrollListener(@NonNull Context context) { - this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in); - this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out); - - this.scrollButtonInAnimation.setDuration(100); - this.scrollButtonOutAnimation.setDuration(50); + private void scrollToStartingPosition(final int startingPosition) { + list.post(() -> { + list.getLayoutManager().scrollToPosition(startingPosition); + getListAdapter().pulseHighlightItem(startingPosition); + }); } - @Override - public void onScrolled(final RecyclerView rv, final int dx, final int dy) { - boolean currentlyAtBottom = isAtBottom(); - boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight(); - int positionId = getHeaderPositionId(); + private void scrollToLastSeenPosition(final int lastSeenPosition) { + //TODO: consider if we want that or not + // if (lastSeenPosition > 0) { + // list.post(() -> ((LinearLayoutManager)list.getLayoutManager()).scrollToPositionWithOffset(lastSeenPosition, list.getHeight())); + // } + } - if (currentlyAtBottom && !wasAtBottom) { - ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE); - } + public interface ConversationFragmentListener { + void setChatId(int threadId); + void handleReplyMessage(DcMsg messageRecord); + } - if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) { - ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation); - } + private class ConversationScrollListener extends OnScrollListener { + + private final Animation scrollButtonInAnimation; + private final Animation scrollButtonOutAnimation; + + private boolean wasAtBottom = true; + private boolean wasAtZoomScrollHeight = false; + private long lastPositionId = -1; + + ConversationScrollListener(@NonNull Context context) { + this.scrollButtonInAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_in); + this.scrollButtonOutAnimation = AnimationUtils.loadAnimation(context, R.anim.fade_scale_out); + + this.scrollButtonInAnimation.setDuration(100); + this.scrollButtonOutAnimation.setDuration(50); + } + + @Override + public void onScrolled(final RecyclerView rv, final int dx, final int dy) { + boolean currentlyAtBottom = isAtBottom(); + boolean currentlyAtZoomScrollHeight = isAtZoomScrollHeight(); + int positionId = getHeaderPositionId(); + + if (currentlyAtBottom && !wasAtBottom) { + ViewUtil.animateOut(scrollToBottomButton, scrollButtonOutAnimation, View.INVISIBLE); + } + + if (currentlyAtZoomScrollHeight && !wasAtZoomScrollHeight) { + ViewUtil.animateIn(scrollToBottomButton, scrollButtonInAnimation); + } // if (positionId != lastPositionId) { // bindScrollHeader(conversationDateHeader, positionId); // } - wasAtBottom = currentlyAtBottom; - wasAtZoomScrollHeight = currentlyAtZoomScrollHeight; - lastPositionId = positionId; + wasAtBottom = currentlyAtBottom; + wasAtZoomScrollHeight = currentlyAtZoomScrollHeight; + lastPositionId = positionId; - markseenDebouncer.publish(() -> manageMessageSeenState()); - } + markseenDebouncer.publish(() -> manageMessageSeenState()); + } - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { // if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { // conversationDateHeader.show(); // } else if (newState == RecyclerView.SCROLL_STATE_IDLE) { // conversationDateHeader.hide(); // } - } - - private boolean isAtBottom() { - if (list.getChildCount() == 0) return true; - - View bottomView = list.getChildAt(0); - int firstVisibleItem = ((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition(); - boolean isAtBottom = (firstVisibleItem == 0); - - return isAtBottom && bottomView.getBottom() <= list.getHeight(); - } - - private boolean isAtZoomScrollHeight() { - return ((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition() > 4; - } - - private int getHeaderPositionId() { - return ((LinearLayoutManager)list.getLayoutManager()).findLastVisibleItemPosition(); - } - - private void bindScrollHeader(HeaderViewHolder headerViewHolder, int positionId) { - if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) { - ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId); - } - } - } - - private void manageMessageSeenState() { - - LinearLayoutManager layoutManager = (LinearLayoutManager)list.getLayoutManager(); - - int firstPos = layoutManager.findFirstVisibleItemPosition(); - int lastPos = layoutManager.findLastVisibleItemPosition(); - if(firstPos==RecyclerView.NO_POSITION || lastPos==RecyclerView.NO_POSITION) { - return; - } - - int[] ids = new int[lastPos - firstPos + 1]; - int index = 0; - for(int pos = firstPos; pos <= lastPos; pos++) { - DcMsg message = ((ConversationAdapter) list.getAdapter()).getMsg(pos); - if (message.getFromId() != DC_CONTACT_ID_SELF && !message.isSeen()) { - ids[index] = message.getId(); - index++; - } - } - dcContext.markseenMsgs(ids); - } - - - void querySetupCode(final DcMsg dcMsg, String[] preload) - { - if( !dcMsg.isSetupMessage()) { - return; - } - - View gl = View.inflate(getActivity(), R.layout.setup_code_grid, null); - final EditText[] editTexts = { - (EditText) gl.findViewById(R.id.setupCode0), (EditText) gl.findViewById(R.id.setupCode1), (EditText) gl.findViewById(R.id.setupCode2), - (EditText) gl.findViewById(R.id.setupCode3), (EditText) gl.findViewById(R.id.setupCode4), (EditText) gl.findViewById(R.id.setupCode5), - (EditText) gl.findViewById(R.id.setupCode6), (EditText) gl.findViewById(R.id.setupCode7), (EditText) gl.findViewById(R.id.setupCode8) - }; - AlertDialog.Builder builder1 = new AlertDialog.Builder(getActivity()); - builder1.setView(gl); - editTexts[0].setText(dcMsg.getSetupCodeBegin()); - editTexts[0].setSelection(editTexts[0].getText().length()); - - for( int i = 0; i < 9; i++ ) { - if( preload != null && i < preload.length ) { - editTexts[i].setText(preload[i]); - editTexts[i].setSelection(editTexts[i].getText().length()); - } - editTexts[i].addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if( s.length()==4 ) { - for ( int i = 0; i < 8; i++ ) { - if( editTexts[i].hasFocus() && editTexts[i+1].getText().length()<4 ) { - editTexts[i+1].requestFocus(); - break; - } + private boolean isAtBottom() { + if (list.getChildCount() == 0) return true; + + View bottomView = list.getChildAt(0); + int firstVisibleItem = ((LinearLayoutManager) list.getLayoutManager()).findFirstVisibleItemPosition(); + boolean isAtBottom = (firstVisibleItem == 0); + + return isAtBottom && bottomView.getBottom() <= list.getHeight(); + } + + private boolean isAtZoomScrollHeight() { + return ((LinearLayoutManager) list.getLayoutManager()).findFirstCompletelyVisibleItemPosition() > 4; + } + + private int getHeaderPositionId() { + return ((LinearLayoutManager)list.getLayoutManager()).findLastVisibleItemPosition(); + } + + private void bindScrollHeader(HeaderViewHolder headerViewHolder, int positionId) { + if (((ConversationAdapter)list.getAdapter()).getHeaderId(positionId) != -1) { + ((ConversationAdapter) list.getAdapter()).onBindHeaderViewHolder(headerViewHolder, positionId); + } + } + } + + private void manageMessageSeenState() { + + LinearLayoutManager layoutManager = (LinearLayoutManager)list.getLayoutManager(); + + int firstPos = layoutManager.findFirstVisibleItemPosition(); + int lastPos = layoutManager.findLastVisibleItemPosition(); + if(firstPos == RecyclerView.NO_POSITION || lastPos == RecyclerView.NO_POSITION) { + return; + } + + int[] ids = new int[lastPos - firstPos + 1]; + int index = 0; + for(int pos = firstPos; pos <= lastPos; pos++) { + DcMsg message = ((ConversationAdapter) list.getAdapter()).getMsg(pos); + if (message.getFromId() != DC_CONTACT_ID_SELF && !message.isSeen()) { + ids[index] = message.getId(); + index++; + } + } + dcContext.markseenMsgs(ids); + } + + + void querySetupCode(final DcMsg dcMsg, String[] preload) + { + if( !dcMsg.isSetupMessage()) { + return; + } + + View gl = View.inflate(getActivity(), R.layout.setup_code_grid, null); + final EditText[] editTexts = { + gl.findViewById(R.id.setupCode0), gl.findViewById(R.id.setupCode1), gl.findViewById(R.id.setupCode2), + gl.findViewById(R.id.setupCode3), gl.findViewById(R.id.setupCode4), gl.findViewById(R.id.setupCode5), + gl.findViewById(R.id.setupCode6), gl.findViewById(R.id.setupCode7), gl.findViewById(R.id.setupCode8) + }; + AlertDialog.Builder builder1 = new AlertDialog.Builder(getActivity()); + builder1.setView(gl); + editTexts[0].setText(dcMsg.getSetupCodeBegin()); + editTexts[0].setSelection(editTexts[0].getText().length()); + + for( int i = 0; i < 9; i++ ) { + if( preload != null && i < preload.length ) { + editTexts[i].setText(preload[i]); + editTexts[i].setSelection(editTexts[i].getText().length()); + } + editTexts[i].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if( s.length()==4 ) { + for ( int i = 0; i < 8; i++ ) { + if( editTexts[i].hasFocus() && editTexts[i+1].getText().length()<4 ) { + editTexts[i+1].requestFocus(); + break; + } + } + } + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + builder1.setTitle(getActivity().getString(R.string.autocrypt_continue_transfer_title)); + builder1.setMessage(getActivity().getString(R.string.autocrypt_continue_transfer_please_enter_code)); + builder1.setNegativeButton(android.R.string.cancel, null); + builder1.setCancelable(false); // prevent the dialog from being dismissed accidentally (when the dialog is closed, the setup code is gone forever and the user has to create a new setup message) + builder1.setPositiveButton(android.R.string.ok, (dialog, which) -> { + String setup_code = ""; + final String[] preload1 = new String[9]; + for ( int i = 0; i < 9; i++ ) { + preload1[i] = editTexts[i].getText().toString(); + setup_code += preload1[i]; + } + boolean success = dcContext.continueKeyTransfer(dcMsg.getId(), setup_code); + + AlertDialog.Builder builder2 = new AlertDialog.Builder(getActivity()); + builder2.setTitle(getActivity().getString(R.string.autocrypt_continue_transfer_title)); + builder2.setMessage(getActivity().getString(success? R.string.autocrypt_continue_transfer_succeeded : R.string.autocrypt_bad_setup_code)); + if( success ) { + builder2.setPositiveButton(android.R.string.ok, null); + } + else { + builder2.setNegativeButton(android.R.string.cancel, null); + builder2.setPositiveButton(R.string.autocrypt_continue_transfer_retry, (dialog1, which1) -> querySetupCode(dcMsg, preload1)); + } + builder2.show(); + }); + builder1.show(); + } + + private class ConversationFragmentItemClickListener implements ItemClickListener { + + @Override + public void onItemClick(DcMsg messageRecord) { + if (actionMode != null) { + ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); + list.getAdapter().notifyDataSetChanged(); + + if (getListAdapter().getSelectedItems().size() == 0) { + actionMode.finish(); + } else { + setCorrectMenuVisibility(actionMode.getMenu()); + actionMode.setTitle(String.valueOf(getListAdapter().getSelectedItems().size())); + } + } + else if(messageRecord.isSetupMessage()) { + querySetupCode(messageRecord,null); } - } } @Override - public void afterTextChanged(Editable s) { + public void onItemLongClick(DcMsg messageRecord) { + if (actionMode == null) { + ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); + list.getAdapter().notifyDataSetChanged(); + + actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); + } } - }); - } - builder1.setTitle(getActivity().getString(R.string.autocrypt_continue_transfer_title)); - builder1.setMessage(getActivity().getString(R.string.autocrypt_continue_transfer_please_enter_code)); - builder1.setNegativeButton(android.R.string.cancel, null); - builder1.setCancelable(false); // prevent the dialog from being dismissed accidentally (when the dialog is closed, the setup code is gone forever and the user has to create a new setup message) - builder1.setPositiveButton(android.R.string.ok, (dialog, which) -> { - String setup_code = ""; - final String[] preload1 = new String[9]; - for ( int i = 0; i < 9; i++ ) { - preload1[i] = editTexts[i].getText().toString(); - setup_code += preload1[i]; - } - boolean success = dcContext.continueKeyTransfer(dcMsg.getId(), setup_code); - - AlertDialog.Builder builder2 = new AlertDialog.Builder(getActivity()); - builder2.setTitle(getActivity().getString(R.string.autocrypt_continue_transfer_title)); - builder2.setMessage(getActivity().getString(success? R.string.autocrypt_continue_transfer_succeeded : R.string.autocrypt_bad_setup_code)); - if( success ) { - builder2.setPositiveButton(android.R.string.ok, null); - } - else { - builder2.setNegativeButton(android.R.string.cancel, null); - builder2.setPositiveButton(R.string.autocrypt_continue_transfer_retry, (dialog1, which1) -> querySetupCode(dcMsg, preload1)); - } - builder2.show(); - }); - builder1.show(); - } - - private class ConversationFragmentItemClickListener implements ItemClickListener { - - @Override - public void onItemClick(DcMsg messageRecord) { - if (actionMode != null) { - ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); - list.getAdapter().notifyDataSetChanged(); - - if (getListAdapter().getSelectedItems().size() == 0) { - actionMode.finish(); - } else { - setCorrectMenuVisibility(actionMode.getMenu()); - actionMode.setTitle(String.valueOf(getListAdapter().getSelectedItems().size())); - } - } - else if(messageRecord.isSetupMessage()) { - querySetupCode(messageRecord,null); - } - } - - @Override - public void onItemLongClick(DcMsg messageRecord) { - if (actionMode == null) { - ((ConversationAdapter) list.getAdapter()).toggleSelection(messageRecord); - list.getAdapter().notifyDataSetChanged(); - - actionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(actionModeCallback); - } - } - - @Override - public void onMessageSharedContactClicked(@NonNull List choices) { - if (getContext() == null) return; + @Override + public void onMessageSharedContactClicked(@NonNull List choices) { + if (getContext() == null) return; // ContactUtil.selectRecipientThroughDialog(getContext(), choices, locale, recipient -> { // CommunicationActions.startConversation(getContext(), recipient, null); // }); + } + + @Override + public void onInviteSharedContactClicked(@NonNull List choices) { + if (getContext() == null) return; + } } @Override - public void onInviteSharedContactClicked(@NonNull List choices) { - if (getContext() == null) return; - } - } + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == CODE_ADD_EDIT_CONTACT && getContext() != null) { + if (requestCode == CODE_ADD_EDIT_CONTACT && getContext() != null) { // ApplicationContext.getInstance(getContext().getApplicationContext()) // .getJobManager() // .add(new DirectoryRefreshJob(getContext().getApplicationContext(), false)); + } } - } - private class ActionModeCallback implements ActionMode.Callback { + private class ActionModeCallback implements ActionMode.Callback { - private int statusBarColor; + private int statusBarColor; - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.conversation_context, menu); + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.conversation_context, menu); - mode.setTitle("1"); + mode.setTitle("1"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - Window window = getActivity().getWindow(); - statusBarColor = window.getStatusBarColor(); - window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar)); - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getActivity().getWindow(); + statusBarColor = window.getStatusBarColor(); + window.setStatusBarColor(getResources().getColor(R.color.action_mode_status_bar)); + } - setCorrectMenuVisibility(menu); - return true; + setCorrectMenuVisibility(menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + ((ConversationAdapter)list.getAdapter()).clearSelection(); + list.getAdapter().notifyDataSetChanged(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getActivity().getWindow().setStatusBarColor(statusBarColor); + } + + actionMode = null; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_context_copy: + handleCopyMessage(getListAdapter().getSelectedItems()); + actionMode.finish(); + return true; + case R.id.menu_context_delete_message: + handleDeleteMessages(getListAdapter().getSelectedItems()); + return true; + case R.id.menu_context_details: + handleDisplayDetails(getSelectedMessageRecord()); + actionMode.finish(); + return true; + case R.id.menu_context_forward: + handleForwardMessage(getListAdapter().getSelectedItems()); + actionMode.finish(); + return true; + case R.id.menu_context_resend: + handleResendMessage(getSelectedMessageRecord()); + actionMode.finish(); + return true; + case R.id.menu_context_save_attachment: + handleSaveAttachment(getSelectedMessageRecord()); + return true; + case R.id.menu_context_reply: + handleReplyMessage(getSelectedMessageRecord()); + actionMode.finish(); + return true; + } + + return false; + } } @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - return false; + public void handleEvent(int eventId, Object data1, Object data2) { + if (eventId == DcContext.DC_EVENT_CHAT_MODIFIED) { + updateLocationButton(); + } + + if (eventId == DcContext.DC_EVENT_INCOMING_MSG && isResumed()) { + setLastSeen(-1); + } + + reloadList(); } - - @Override - public void onDestroyActionMode(ActionMode mode) { - ((ConversationAdapter)list.getAdapter()).clearSelection(); - list.getAdapter().notifyDataSetChanged(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - getActivity().getWindow().setStatusBarColor(statusBarColor); - } - - actionMode = null; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch(item.getItemId()) { - case R.id.menu_context_copy: - handleCopyMessage(getListAdapter().getSelectedItems()); - actionMode.finish(); - return true; - case R.id.menu_context_delete_message: - handleDeleteMessages(getListAdapter().getSelectedItems()); - return true; - case R.id.menu_context_details: - handleDisplayDetails(getSelectedMessageRecord()); - actionMode.finish(); - return true; - case R.id.menu_context_forward: - handleForwardMessage(getListAdapter().getSelectedItems()); - actionMode.finish(); - return true; - case R.id.menu_context_resend: - handleResendMessage(getSelectedMessageRecord()); - actionMode.finish(); - return true; - case R.id.menu_context_save_attachment: - handleSaveAttachment(getSelectedMessageRecord()); - return true; - case R.id.menu_context_reply: - handleReplyMessage(getSelectedMessageRecord()); - actionMode.finish(); - return true; - } - - return false; - } - } - - @Override - public void handleEvent(int eventId, Object data1, Object data2) { - if (eventId == DcContext.DC_EVENT_CHAT_MODIFIED) { - updateLocationButton(); - } - - reloadList(); - } } diff --git a/src/org/thoughtcrime/securesms/ConversationListActivity.java b/src/org/thoughtcrime/securesms/ConversationListActivity.java index 66cbc118b..bbf430850 100644 --- a/src/org/thoughtcrime/securesms/ConversationListActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListActivity.java @@ -41,7 +41,6 @@ import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.Prefs; import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA; -import static org.thoughtcrime.securesms.ConversationActivity.LAST_SEEN_EXTRA; import static org.thoughtcrime.securesms.ConversationActivity.STARTING_POSITION_EXTRA; import static org.thoughtcrime.securesms.map.MapDataManager.ALL_CHATS_GLOBAL_MAP; import static org.thoughtcrime.securesms.util.RelayUtil.REQUEST_RELAY; @@ -217,16 +216,15 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit } @Override - public void onCreateConversation(int chatId, long lastSeen) { - openConversation(chatId, lastSeen, -1); + public void onCreateConversation(int chatId) { + openConversation(chatId, -1); } - public void openConversation(int chatId, long lastSeen, int startingPosition) { + public void openConversation(int chatId, int startingPosition) { searchToolbar.clearFocus(); Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(CHAT_ID_EXTRA, chatId); - intent.putExtra(LAST_SEEN_EXTRA, lastSeen); intent.putExtra(STARTING_POSITION_EXTRA, startingPosition); if (isRelayingMessageContent(this)) { acquireRelayMessageContent(this, intent); diff --git a/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java b/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java index 1fddc630d..fd121dda4 100644 --- a/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java +++ b/src/org/thoughtcrime/securesms/ConversationListArchiveActivity.java @@ -9,7 +9,6 @@ import org.thoughtcrime.securesms.util.DynamicTheme; import static org.thoughtcrime.securesms.ConversationActivity.CHAT_ID_EXTRA; import static org.thoughtcrime.securesms.ConversationActivity.IS_ARCHIVED_EXTRA; -import static org.thoughtcrime.securesms.ConversationActivity.LAST_SEEN_EXTRA; import static org.thoughtcrime.securesms.util.RelayUtil.REQUEST_RELAY; import static org.thoughtcrime.securesms.util.RelayUtil.acquireRelayMessageContent; import static org.thoughtcrime.securesms.util.RelayUtil.isRelayingMessageContent; @@ -63,11 +62,10 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar } @Override - public void onCreateConversation(int chatId, long lastSeenTime) { + public void onCreateConversation(int chatId) { Intent intent = new Intent(this, ConversationActivity.class); intent.putExtra(CHAT_ID_EXTRA, chatId); intent.putExtra(IS_ARCHIVED_EXTRA, true); - intent.putExtra(LAST_SEEN_EXTRA, lastSeenTime); if (isRelayingMessageContent(this)) { acquireRelayMessageContent(this, intent); startActivityForResult(intent, REQUEST_RELAY); diff --git a/src/org/thoughtcrime/securesms/ConversationListFragment.java b/src/org/thoughtcrime/securesms/ConversationListFragment.java index 5e0509d10..16e9dc6ba 100644 --- a/src/org/thoughtcrime/securesms/ConversationListFragment.java +++ b/src/org/thoughtcrime/securesms/ConversationListFragment.java @@ -341,8 +341,8 @@ public class ConversationListFragment extends Fragment actionMode.setTitle(String.valueOf(getListAdapter().getBatchSelections().size())); } - private void handleCreateConversation(int threadId, long lastSeen) { - ((ConversationSelectedListener)getActivity()).onCreateConversation(threadId, lastSeen); + private void handleCreateConversation(int chatId) { + ((ConversationSelectedListener)getActivity()).onCreateConversation(chatId); } @Override @@ -413,7 +413,7 @@ public class ConversationListFragment extends Fragment .setPositiveButton(android.R.string.ok, (dialog, which) -> { int belongingChatId = dcContext.createChatByMsgId(msgId); if( belongingChatId != 0 ) { - handleCreateConversation(belongingChatId, 0); + handleCreateConversation(belongingChatId); } }) .setNegativeButton(R.string.not_now, null) @@ -424,7 +424,7 @@ public class ConversationListFragment extends Fragment return; } - handleCreateConversation(chatId, 0); + handleCreateConversation(chatId); } else { ConversationListAdapter adapter = (ConversationListAdapter)list.getAdapter(); adapter.toggleThreadInBatchSet(item.getChatId()); @@ -454,7 +454,7 @@ public class ConversationListFragment extends Fragment } public interface ConversationSelectedListener { - void onCreateConversation(int threadId, long lastSeen); + void onCreateConversation(int chatId); void onSwitchToArchive(); } diff --git a/src/org/thoughtcrime/securesms/map/MapActivity.java b/src/org/thoughtcrime/securesms/map/MapActivity.java index 152e6f202..5af2fc092 100644 --- a/src/org/thoughtcrime/securesms/map/MapActivity.java +++ b/src/org/thoughtcrime/securesms/map/MapActivity.java @@ -297,7 +297,6 @@ public class MapActivity extends BaseActivity implements Observer, TimeRangeSlid Intent intent = new Intent(MapActivity.this, ConversationActivity.class); intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcMsgChatId); - intent.putExtra(ConversationActivity.LAST_SEEN_EXTRA, 0); intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, startingPosition); startActivity(intent); return true; diff --git a/src/org/thoughtcrime/securesms/search/SearchFragment.java b/src/org/thoughtcrime/securesms/search/SearchFragment.java index b49514c6f..44e8a0902 100644 --- a/src/org/thoughtcrime/securesms/search/SearchFragment.java +++ b/src/org/thoughtcrime/securesms/search/SearchFragment.java @@ -133,7 +133,7 @@ public class SearchFragment extends Fragment implements SearchListAdapter.EventL public void onConversationClicked(@NonNull DcChatlist.Item chatlistItem) { ConversationListActivity conversationList = (ConversationListActivity) getActivity(); if (conversationList != null) { - conversationList.onCreateConversation(chatlistItem.chatId,0); + conversationList.onCreateConversation(chatlistItem.chatId); } } @@ -150,11 +150,11 @@ public class SearchFragment extends Fragment implements SearchListAdapter.EventL .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(android.R.string.ok, (dialog, which) -> { int chatId1 = dcContext.createChatByContactId(contact.getId()); - conversationList.onCreateConversation(chatId1,0); + conversationList.onCreateConversation(chatId1); }).show(); } else { - conversationList.onCreateConversation(chatId,0); + conversationList.onCreateConversation(chatId); } } } @@ -174,7 +174,7 @@ public class SearchFragment extends Fragment implements SearchListAdapter.EventL break; } } - conversationList.openConversation(chatId, 0, startingPosition); + conversationList.openConversation(chatId, startingPosition); } }