package com.sendbird.syncmanager.sample.groupchannel; import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; import com.sendbird.android.AdminMessage; import com.sendbird.android.BaseChannel; import com.sendbird.android.BaseMessage; import com.sendbird.android.FileMessage; import com.sendbird.android.GroupChannel; import com.sendbird.android.Member; import com.sendbird.android.SendBird; import com.sendbird.android.SendBirdException; import com.sendbird.android.UserMessage; import com.sendbird.syncmanager.FailedMessageEventActionReason; import com.sendbird.syncmanager.MessageCollection; import com.sendbird.syncmanager.MessageEventAction; import com.sendbird.syncmanager.MessageFilter; import com.sendbird.syncmanager.handler.CompletionHandler; import com.sendbird.syncmanager.handler.FetchCompletionHandler; import com.sendbird.syncmanager.handler.MessageCollectionCreateHandler; import com.sendbird.syncmanager.handler.MessageCollectionHandler; import com.sendbird.syncmanager.sample.R; import com.sendbird.syncmanager.sample.utils.FileUtils; import com.sendbird.syncmanager.sample.utils.MediaPlayerActivity; import com.sendbird.syncmanager.sample.utils.PhotoViewerActivity; import com.sendbird.syncmanager.sample.utils.PreferenceUtils; import com.sendbird.syncmanager.sample.utils.TextUtils; import com.sendbird.syncmanager.sample.utils.UrlPreviewInfo; import com.sendbird.syncmanager.sample.utils.WebUtils; import org.json.JSONException; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Hashtable; import java.util.List; public class GroupChatFragment extends Fragment { private static final String LOG_TAG = GroupChatFragment.class.getSimpleName(); private static final String CONNECTION_HANDLER_ID = "CONNECTION_HANDLER_GROUP_CHAT"; private static final String CHANNEL_HANDLER_ID = "CHANNEL_HANDLER_GROUP_CHANNEL_CHAT"; private static final int STATE_NORMAL = 0; private static final int STATE_EDIT = 1; private static final String STATE_CHANNEL_URL = "STATE_CHANNEL_URL"; private static final int INTENT_REQUEST_CHOOSE_MEDIA = 301; private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 13; static final String EXTRA_CHANNEL_URL = "EXTRA_CHANNEL_URL"; static final String EXTRA_CHANNEL = "EXTRA_CHANNEL"; private InputMethodManager mIMM; private RelativeLayout mRootLayout; private RecyclerView mRecyclerView; private GroupChatAdapter mChatAdapter; private LinearLayoutManager mLayoutManager; private EditText mMessageEditText; private Button mMessageSendButton; private ImageButton mUploadFileButton; private View mCurrentEventLayout; private TextView mCurrentEventText; private TextView mNewMessageText; private GroupChannel mChannel; private String mChannelUrl; private int mCurrentState = STATE_NORMAL; private BaseMessage mEditingMessage = null; final MessageFilter mMessageFilter = new MessageFilter(BaseChannel.MessageTypeFilter.ALL, null, null); private MessageCollection mMessageCollection; private long mLastRead; /** * To create an instance of this fragment, a Channel URL should be required. */ public static GroupChatFragment newInstance(@NonNull String channelUrl) { GroupChatFragment fragment = new GroupChatFragment(); Bundle args = new Bundle(); args.putString(GroupChannelListFragment.EXTRA_GROUP_CHANNEL_URL, channelUrl); fragment.setArguments(args); return fragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mIMM = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); if (savedInstanceState != null) { mChannelUrl = savedInstanceState.getString(STATE_CHANNEL_URL); } else { if (getArguments() != null) { mChannelUrl = getArguments().getString(GroupChannelListFragment.EXTRA_GROUP_CHANNEL_URL); } } Log.d(LOG_TAG, mChannelUrl); mLastRead = PreferenceUtils.getLastRead(mChannelUrl); mChatAdapter = new GroupChatAdapter(getActivity()); setUpChatListAdapter(); } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_group_chat, container, false); setRetainInstance(true); mRootLayout = (RelativeLayout) rootView.findViewById(R.id.layout_group_chat_root); mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_group_chat); mCurrentEventLayout = rootView.findViewById(R.id.layout_group_chat_current_event); mCurrentEventText = (TextView) rootView.findViewById(R.id.text_group_chat_current_event); mMessageEditText = (EditText) rootView.findViewById(R.id.edittext_group_chat_message); mMessageSendButton = (Button) rootView.findViewById(R.id.button_group_chat_send); mUploadFileButton = (ImageButton) rootView.findViewById(R.id.button_group_chat_upload); mNewMessageText = (TextView) rootView.findViewById(R.id.text_group_chat_new_message); mMessageEditText.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) { } @Override public void afterTextChanged(Editable s) { if (s.length() > 0) { mMessageSendButton.setEnabled(true); } else { mMessageSendButton.setEnabled(false); } } }); mMessageSendButton.setEnabled(false); mMessageSendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mCurrentState == STATE_EDIT) { String userInput = mMessageEditText.getText().toString(); if (userInput.length() > 0 && mEditingMessage != null) { editMessage(mEditingMessage, userInput); } setState(STATE_NORMAL, null, -1); } else { String userInput = mMessageEditText.getText().toString(); if (userInput.length() == 0) { return; } sendUserMessage(userInput); mMessageEditText.setText(""); mRecyclerView.scrollToPosition(0); } } }); mUploadFileButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { requestMedia(); } }); mMessageEditText.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() == 0) { setTypingStatus(false); } else { setTypingStatus(true); } } @Override public void afterTextChanged(Editable s) { } }); mNewMessageText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mNewMessageText.setVisibility(View.GONE); mMessageCollection.resetViewpointTimestamp(Long.MAX_VALUE); fetchInitialMessages(); } }); setUpRecyclerView(); setHasOptionsMenu(true); createMessageCollection(mChannelUrl); return rootView; } @Override public void onResume() { super.onResume(); mChatAdapter.setContext(getActivity()); // Glide bug fix (java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity)` SendBird.addConnectionHandler(CONNECTION_HANDLER_ID, new SendBird.ConnectionHandler() { @Override public void onReconnectStarted() { } @Override public void onReconnectSucceeded() { if (mMessageCollection != null) { if (mLayoutManager.findFirstVisibleItemPosition() <= 0) { mMessageCollection.fetchAllNextMessages(new FetchCompletionHandler() { @Override public void onCompleted(boolean hasMore, SendBirdException e) { } }); } if (mLayoutManager.findLastVisibleItemPosition() == mChatAdapter.getItemCount() - 1) { mMessageCollection.fetchSucceededMessages(MessageCollection.Direction.PREVIOUS, new FetchCompletionHandler() { @Override public void onCompleted(boolean hasMore, SendBirdException e) { } }); } } } @Override public void onReconnectFailed() { } }); SendBird.addChannelHandler(CHANNEL_HANDLER_ID, new SendBird.ChannelHandler() { @Override public void onMessageReceived(BaseChannel baseChannel, BaseMessage baseMessage) { } @Override public void onReadReceiptUpdated(GroupChannel channel) { if (channel.getUrl().equals(mChannelUrl)) { mChatAdapter.notifyDataSetChanged(); } } @Override public void onTypingStatusUpdated(GroupChannel channel) { if (channel.getUrl().equals(mChannelUrl)) { List<Member> typingUsers = channel.getTypingMembers(); displayTyping(typingUsers); } } @Override public void onDeliveryReceiptUpdated(GroupChannel channel) { if (channel.getUrl().equals(mChannelUrl)) { mChatAdapter.notifyDataSetChanged(); } } }); } @Override public void onPause() { setTypingStatus(false); displayTyping(null); SendBird.removeConnectionHandler(CONNECTION_HANDLER_ID); SendBird.removeChannelHandler(CHANNEL_HANDLER_ID); super.onPause(); } @Override public void onDestroy() { // Save messages to cache. if (mMessageCollection != null) { mMessageCollection.setCollectionHandler(null); mMessageCollection.remove(); } super.onDestroy(); } @Override public void onSaveInstanceState(Bundle outState) { outState.putString(STATE_CHANNEL_URL, mChannelUrl); super.onSaveInstanceState(outState); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.menu_group_chat, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_group_channel_invite) { Intent intent = new Intent(getActivity(), InviteMemberActivity.class); intent.putExtra(EXTRA_CHANNEL_URL, mChannelUrl); startActivity(intent); return true; } else if (id == R.id.action_group_channel_view_members) { Intent intent = new Intent(getActivity(), MemberListActivity.class); intent.putExtra(EXTRA_CHANNEL, mChannel.serialize()); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } @Override public void onActivityResult(int requestCode, int resultCode, final Intent data) { super.onActivityResult(requestCode, resultCode, data); // Set this as true to restore background connection management. SendBird.setAutoBackgroundDetection(true); if (requestCode == INTENT_REQUEST_CHOOSE_MEDIA && resultCode == Activity.RESULT_OK) { // If user has successfully chosen the image, show a dialog to confirm upload. if (data == null) { Log.d(LOG_TAG, "data is null!"); return; } sendFileWithThumbnail(data.getData()); } } @Override public void onAttach(Context context) { super.onAttach(context); ((GroupChannelActivity) context).setOnBackPressedListener(new GroupChannelActivity.onBackPressedListener() { @Override public boolean onBack() { if (mCurrentState == STATE_EDIT) { setState(STATE_NORMAL, null, -1); return true; } mIMM.hideSoftInputFromWindow(mMessageEditText.getWindowToken(), 0); return false; } }); } private void setUpRecyclerView() { mLayoutManager = new LinearLayoutManager(getActivity()); mLayoutManager.setReverseLayout(true); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(mChatAdapter); mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (mLayoutManager.findFirstVisibleItemPosition() == 0) { mMessageCollection.fetchSucceededMessages(MessageCollection.Direction.NEXT, null); mNewMessageText.setVisibility(View.GONE); } if (mLayoutManager.findLastVisibleItemPosition() == mChatAdapter.getItemCount() - 1) { mMessageCollection.fetchSucceededMessages(MessageCollection.Direction.PREVIOUS, null); } } } }); } private void setUpChatListAdapter() { mChatAdapter.setItemClickListener(new GroupChatAdapter.OnItemClickListener() { @Override public void onUserMessageItemClick(UserMessage message) { if (mChatAdapter.isFailedMessage(message) && !mChatAdapter.isResendingMessage(message)) { retryFailedMessage(message); return; } // Message is sending. Do nothing on click event. if (mChatAdapter.isTempMessage(message)) { return; } if (message.getCustomType().equals(GroupChatAdapter.URL_PREVIEW_CUSTOM_TYPE)) { try { UrlPreviewInfo info = new UrlPreviewInfo(message.getData()); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(info.getUrl())); startActivity(browserIntent); } catch (JSONException e) { e.printStackTrace(); } } } @Override public void onFileMessageItemClick(FileMessage message) { // Load media chooser and removeSucceededMessages the failed message from list. if (mChatAdapter.isFailedMessage(message)) { retryFailedMessage(message); return; } // Message is sending. Do nothing on click event. if (mChatAdapter.isTempMessage(message)) { return; } onFileMessageClicked(message); } }); mChatAdapter.setItemLongClickListener(new GroupChatAdapter.OnItemLongClickListener() { @Override public void onUserMessageItemLongClick(UserMessage message, int position) { if (message.getSender().getUserId().equals(PreferenceUtils.getUserId())) { showMessageOptionsDialog(message, position); } } @Override public void onFileMessageItemLongClick(FileMessage message) { } @Override public void onAdminMessageItemLongClick(AdminMessage message) { } }); } private void showMessageOptionsDialog(final BaseMessage message, final int position) { String[] options; if (message.getMessageId() == 0) { options = new String[]{getString(R.string.option_delete_message)}; } else { options = new String[]{getString(R.string.option_edit_message), getString(R.string.option_delete_message)}; } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setItems(options, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (options.length == 1) { deleteMessage(message); } else { if (which == 0) { setState(STATE_EDIT, message, position); } else if (which == 1) { deleteMessage(message); } } } }); builder.create().show(); } private void setState(int state, BaseMessage editingMessage, final int position) { switch (state) { case STATE_NORMAL: mCurrentState = STATE_NORMAL; mEditingMessage = null; mUploadFileButton.setVisibility(View.VISIBLE); mMessageSendButton.setText(getString(R.string.action_send_message)); mMessageEditText.setText(""); break; case STATE_EDIT: mCurrentState = STATE_EDIT; mEditingMessage = editingMessage; mUploadFileButton.setVisibility(View.GONE); mMessageSendButton.setText(getString(R.string.action_update_message)); String messageString = ((UserMessage) editingMessage).getMessage(); if (messageString == null) { messageString = ""; } mMessageEditText.setText(messageString); if (messageString.length() > 0) { mMessageEditText.setSelection(0, messageString.length()); } mMessageEditText.requestFocus(); mMessageEditText.postDelayed(new Runnable() { @Override public void run() { mIMM.showSoftInput(mMessageEditText, 0); mRecyclerView.postDelayed(new Runnable() { @Override public void run() { mRecyclerView.scrollToPosition(position); } }, 500); } }, 100); break; } } private void createMessageCollection(final String channelUrl) { GroupChannel.getChannel(channelUrl, new GroupChannel.GroupChannelGetHandler() { @Override public void onResult(GroupChannel groupChannel, SendBirdException e) { if (e != null) { MessageCollection.create(channelUrl, mMessageFilter, mLastRead, new MessageCollectionCreateHandler() { @Override public void onResult(MessageCollection messageCollection, SendBirdException e) { if (e == null) { if (mMessageCollection != null) { mMessageCollection.remove(); } mMessageCollection = messageCollection; mMessageCollection.setCollectionHandler(mMessageCollectionHandler); mChannel = mMessageCollection.getChannel(); mChatAdapter.setChannel(mChannel); if (getActivity() == null) { return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { mChatAdapter.clear(); updateActionBarTitle(); } }); fetchInitialMessages(); } else { Toast.makeText(getContext(), getString(R.string.get_channel_failed), Toast.LENGTH_SHORT).show(); new Handler().postDelayed(new Runnable() { @Override public void run() { getActivity().onBackPressed(); } }, 1000); } } }); } else { if (mMessageCollection != null) { mMessageCollection.remove(); } mMessageCollection = new MessageCollection(groupChannel, mMessageFilter, mLastRead); mMessageCollection.setCollectionHandler(mMessageCollectionHandler); mChannel = groupChannel; mChatAdapter.setChannel(mChannel); mChatAdapter.clear(); updateActionBarTitle(); fetchInitialMessages(); } } }); } private void refresh() { if (mChannel != null) { mChannel.refresh(new GroupChannel.GroupChannelRefreshHandler() { @Override public void onResult(SendBirdException e) { if (e != null) { // Error! e.printStackTrace(); return; } updateActionBarTitle(); createMessageCollection(mChannel.getUrl()); } }); } else { createMessageCollection(mChannelUrl); } } private void fetchInitialMessages() { if (mMessageCollection == null) { return; } mMessageCollection.fetchSucceededMessages(MessageCollection.Direction.PREVIOUS, new FetchCompletionHandler() { @Override public void onCompleted(boolean hasMore, SendBirdException e) { mMessageCollection.fetchSucceededMessages(MessageCollection.Direction.NEXT, new FetchCompletionHandler() { @Override public void onCompleted(boolean hasMore, SendBirdException e) { mMessageCollection.fetchFailedMessages(new CompletionHandler() { @Override public void onCompleted(SendBirdException e) { if (getActivity() == null) { return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { mChatAdapter.markAllMessagesAsRead(); mLayoutManager.scrollToPositionWithOffset(mChatAdapter.getLastReadPosition(mLastRead), mRecyclerView.getHeight() / 2); } }); } }); } }); } }); } private void retryFailedMessage(final BaseMessage message) { new AlertDialog.Builder(getActivity()) .setMessage(getString(R.string.request_retry_send_message)) .setPositiveButton(R.string.resend_message, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { if (message instanceof UserMessage) { mChannel.resendUserMessage((UserMessage) message, new BaseChannel.ResendUserMessageHandler() { @Override public void onSent(UserMessage userMessage, SendBirdException e) { mMessageCollection.handleSendMessageResponse(userMessage, e); } }); } else if (message instanceof FileMessage) { Uri uri = mChatAdapter.getTempFileMessageUri(message); sendFileWithThumbnail(uri); mChatAdapter.removeFailedMessages(Collections.singletonList(message)); } } } }) .setNegativeButton(R.string.delete_message, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { if (message instanceof UserMessage) { mMessageCollection.deleteMessage(message); } else if (message instanceof FileMessage) { mChatAdapter.removeFailedMessages(Collections.singletonList(message)); } } } }).show(); } /** * Display which users are typing. * If more than two users are currently typing, this will state that "multiple users" are typing. * * @param typingUsers The list of currently typing users. */ private void displayTyping(List<Member> typingUsers) { if (typingUsers != null && typingUsers.size() > 0) { mCurrentEventLayout.setVisibility(View.VISIBLE); String string; if (typingUsers.size() == 1) { string = String.format(getString(R.string.user_typing), typingUsers.get(0).getNickname()); } else if (typingUsers.size() == 2) { string = String.format(getString(R.string.two_users_typing), typingUsers.get(0).getNickname(), typingUsers.get(1).getNickname()); } else { string = getString(R.string.users_typing); } mCurrentEventText.setText(string); } else { mCurrentEventLayout.setVisibility(View.GONE); } } private void requestMedia() { if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // If storage permissions are not granted, request permissions at run-time, // as per < API 23 guidelines. requestStoragePermissions(); } else { Intent intent = new Intent(); intent.setType("*/*"); intent.setAction(Intent.ACTION_GET_CONTENT); // Always show the chooser (if there are multiple options available) startActivityForResult(Intent.createChooser(intent, "Select Media"), INTENT_REQUEST_CHOOSE_MEDIA); // Set this as false to maintain connection // even when an external Activity is started. SendBird.setAutoBackgroundDetection(false); } } private void requestStoragePermissions() { if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Provide an additional rationale to the user if the permission was not granted // and the user would benefit from additional context for the use of the permission. // For example if the user has previously denied the permission. Snackbar.make(mRootLayout, getString(R.string.request_external_storage), Snackbar.LENGTH_LONG) .setAction(getString(R.string.accept_permission), new View.OnClickListener() { @Override public void onClick(View view) { requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_WRITE_EXTERNAL_STORAGE); } }) .show(); } else { // Permission has not been granted yet. Request it directly. requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_WRITE_EXTERNAL_STORAGE); } } private void onFileMessageClicked(FileMessage message) { String type = message.getType().toLowerCase(); if (type.startsWith("image")) { Intent i = new Intent(getActivity(), PhotoViewerActivity.class); i.putExtra("url", message.getUrl()); i.putExtra("type", message.getType()); startActivity(i); } else if (type.startsWith("video")) { Intent intent = new Intent(getActivity(), MediaPlayerActivity.class); intent.putExtra("url", message.getUrl()); startActivity(intent); } else { showDownloadConfirmDialog(message); } } private void showDownloadConfirmDialog(final FileMessage message) { if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // If storage permissions are not granted, request permissions at run-time, // as per < API 23 guidelines. requestStoragePermissions(); } else { new AlertDialog.Builder(getActivity()) .setMessage(getString(R.string.request_download_file)) .setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { FileUtils.downloadFile(getActivity(), message.getUrl(), message.getName()); } } }) .setNegativeButton(R.string.cancel, null).show(); } } private void updateActionBarTitle() { String title = ""; if (mChannel != null) { title = TextUtils.getGroupChannelTitle(mChannel); } // Set action bar title to name of channel if (getActivity() != null) { ((GroupChannelActivity) getActivity()).setActionBarTitle(title); } } @SuppressLint("StaticFieldLeak") private void sendUserMessageWithUrl(final String text, String url) { if (mChannel == null) { return; } new WebUtils.UrlPreviewAsyncTask() { @Override protected void onPostExecute(UrlPreviewInfo info) { if (mChannel == null) { return; } UserMessage tempUserMessage = null; BaseChannel.SendUserMessageHandler handler = new BaseChannel.SendUserMessageHandler() { @Override public void onSent(UserMessage userMessage, SendBirdException e) { if (e != null) { // Error! Log.e(LOG_TAG, e.toString()); Toast.makeText( getActivity(), getString(R.string.send_message_error, e.getCode(), e.getMessage()), Toast.LENGTH_SHORT) .show(); } mMessageCollection.handleSendMessageResponse(userMessage, e); } }; try { // Sending a message with URL preview information and custom type. String jsonString = info.toJsonString(); tempUserMessage = mChannel.sendUserMessage(text, jsonString, GroupChatAdapter.URL_PREVIEW_CUSTOM_TYPE, handler); } catch (Exception e) { // Sending a message without URL preview information. tempUserMessage = mChannel.sendUserMessage(text, handler); } // Display a user message to RecyclerView if (mMessageCollection != null) { mMessageCollection.appendMessage(tempUserMessage); } } }.execute(url); } private void sendUserMessage(String text) { if (mChannel == null) { return; } List<String> urls = WebUtils.extractUrls(text); if (urls.size() > 0) { sendUserMessageWithUrl(text, urls.get(0)); return; } final UserMessage pendingMessage = mChannel.sendUserMessage(text, new BaseChannel.SendUserMessageHandler() { @Override public void onSent(UserMessage userMessage, SendBirdException e) { if (mMessageCollection != null) { mMessageCollection.handleSendMessageResponse(userMessage, e); mMessageCollection.fetchAllNextMessages(null); } if (e != null) { // Error! Log.e(LOG_TAG, e.toString()); Toast.makeText(getActivity(), getString(R.string.send_message_error, e.getCode(), e.getMessage()), Toast.LENGTH_SHORT).show(); return; } } }); if (mMessageCollection != null) { mMessageCollection.appendMessage(pendingMessage); } } /** * Notify other users whether the current user is typing. * * @param typing Whether the user is currently typing. */ private void setTypingStatus(boolean typing) { if (mChannel == null) { return; } if (typing) { mChannel.startTyping(); } else { mChannel.endTyping(); } } /** * Sends a File Message containing an image file. * Also requests thumbnails to be generated in specified sizes. * * @param uri The URI of the image, which in this case is received through an Intent request. */ private void sendFileWithThumbnail(Uri uri) { if (mChannel == null) { return; } // Specify two dimensions of thumbnails to generate List<FileMessage.ThumbnailSize> thumbnailSizes = new ArrayList<>(); thumbnailSizes.add(new FileMessage.ThumbnailSize(240, 240)); thumbnailSizes.add(new FileMessage.ThumbnailSize(320, 320)); Hashtable<String, Object> info = FileUtils.getFileInfo(getActivity(), uri); if (info == null || info.isEmpty()) { Toast.makeText(getActivity(), getString(R.string.wrong_file_info), Toast.LENGTH_LONG).show(); return; } final String name; if (info.containsKey("name")) { name = (String) info.get("name"); } else { name = "Sendbird File"; } final String path = (String) info.get("path"); final File file = new File(path); final String mime = (String) info.get("mime"); final int size = (int) info.get("size"); if (path == null || path.equals("")) { Toast.makeText(getActivity(), getString(R.string.wrong_file_path), Toast.LENGTH_LONG).show(); } else { BaseChannel.SendFileMessageHandler fileMessageHandler = new BaseChannel.SendFileMessageHandler() { @Override public void onSent(FileMessage fileMessage, SendBirdException e) { mMessageCollection.handleSendMessageResponse(fileMessage, e); mMessageCollection.fetchAllNextMessages(null); if (e != null) { Log.d("MyTag", "onSent: " + getActivity()); if (getActivity() != null) { Toast.makeText(getActivity(), getString(R.string.sendbird_error_with_code, e.getCode(), e.getMessage()), Toast.LENGTH_SHORT).show(); } } } }; // Send image with thumbnails in the specified dimensions FileMessage tempFileMessage = mChannel.sendFileMessage(file, name, mime, size, "", null, thumbnailSizes, fileMessageHandler); mChatAdapter.addTempFileMessageInfo(tempFileMessage, uri); if (mMessageCollection != null) { mMessageCollection.appendMessage(tempFileMessage); } } } private void editMessage(final BaseMessage message, String editedMessage) { if (mChannel == null) { return; } mChannel.updateUserMessage(message.getMessageId(), editedMessage, null, null, new BaseChannel.UpdateUserMessageHandler() { @Override public void onUpdated(UserMessage userMessage, SendBirdException e) { if (e != null) { // Error! Toast.makeText(getActivity(), getString(R.string.sendbird_error_with_code, e.getCode(), e.getMessage()), Toast.LENGTH_SHORT).show(); return; } if (mMessageCollection != null) { mMessageCollection.updateMessage(userMessage); } } }); } /** * Deletes a message within the channel. * Note that users can only delete messages sent by oneself. * * @param message The message to delete. */ private void deleteMessage(final BaseMessage message) { if (message.getMessageId() == 0) { mMessageCollection.deleteMessage(message); } else { if (mChannel == null) { return; } mChannel.deleteMessage(message, new BaseChannel.DeleteMessageHandler() { @Override public void onResult(SendBirdException e) { if (e != null) { // Error! Toast.makeText(getActivity(), getString(R.string.sendbird_error_with_code, e.getCode(), e.getMessage()), Toast.LENGTH_SHORT).show(); return; } mMessageCollection.deleteMessage(message); } }); } } private void updateLastSeenTimestamp(List<BaseMessage> messages) { long lastSeenTimestamp = mLastRead == Long.MAX_VALUE ? 0 : mLastRead; for (BaseMessage message : messages) { if (lastSeenTimestamp < message.getCreatedAt()) { lastSeenTimestamp = message.getCreatedAt(); } } if (lastSeenTimestamp > mLastRead) { PreferenceUtils.setLastRead(mChannelUrl, lastSeenTimestamp); mLastRead = lastSeenTimestamp; } } private MessageCollectionHandler mMessageCollectionHandler = new MessageCollectionHandler() { @Override public void onMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action) { } @Override public void onSucceededMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action) { Log.d("SyncManager", "onSucceededMessageEvent: size = " + messages.size() + ", action = " + action); if (getActivity() == null) { return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { switch (action) { case INSERT: mChatAdapter.insertSucceededMessages(messages); mChatAdapter.markAllMessagesAsRead(); break; case REMOVE: mChatAdapter.removeSucceededMessages(messages); break; case UPDATE: mChatAdapter.updateSucceededMessages(messages); break; case CLEAR: mChatAdapter.clear(); break; } } }); updateLastSeenTimestamp(messages); } @Override public void onPendingMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action) { Log.d("SyncManager", "onPendingMessageEvent: size = " + messages.size() + ", action = " + action); if (getActivity() == null) { return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { switch (action) { case INSERT: List<BaseMessage> pendingMessages = new ArrayList<>(); for (BaseMessage message : messages) { if (!mChatAdapter.failedMessageListContains(message)) { pendingMessages.add(message); } } mChatAdapter.insertSucceededMessages(pendingMessages); break; case REMOVE: mChatAdapter.removeSucceededMessages(messages); break; } } }); } @Override public void onFailedMessageEvent(MessageCollection collection, final List<BaseMessage> messages, final MessageEventAction action, final FailedMessageEventActionReason reason) { Log.d("SyncManager", "onFailedMessageEvent: size = " + messages.size() + ", action = " + action); if (getActivity() == null) { return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { switch (action) { case INSERT: mChatAdapter.insertFailedMessages(messages); break; case REMOVE: mChatAdapter.removeFailedMessages(messages); break; case UPDATE: if (reason == FailedMessageEventActionReason.UPDATE_RESEND_FAILED) { mChatAdapter.updateFailedMessages(messages); } break; } } }); } @Override public void onNewMessage(MessageCollection collection, BaseMessage message) { Log.d("SyncManager", "onNewMessage: message = " + message); //show when the scroll position is bottom ONLY. if (mLayoutManager.findFirstVisibleItemPosition() != 0) { if (message instanceof UserMessage) { if (!((UserMessage) message).getSender().getUserId().equals(PreferenceUtils.getUserId())) { mNewMessageText.setText("New Message = " + ((UserMessage) message).getSender().getNickname() + " : " + ((UserMessage) message).getMessage()); mNewMessageText.setVisibility(View.VISIBLE); } } else if (message instanceof FileMessage) { if (!((FileMessage) message).getSender().getUserId().equals(PreferenceUtils.getUserId())) { mNewMessageText.setText("New Message = " + ((FileMessage) message).getSender().getNickname() + "Send a File"); mNewMessageText.setVisibility(View.VISIBLE); } } } } }; }