diff options
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java')
-rw-r--r-- | src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java | 1383 |
1 files changed, 0 insertions, 1383 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java deleted file mode 100644 index 157e6dc3..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java +++ /dev/null @@ -1,1383 +0,0 @@ -package de.thedevstack.conversationsplus.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Fragment; -import android.app.PendingIntent; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentSender.SendIntentException; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.text.InputType; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.AdapterView; -import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.PopupWindow; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; -import android.widget.Toast; - -import com.orangegangsters.github.swipyrefreshlayout.library.SwipyRefreshLayout; - -import net.java.otr4j.session.SessionStatus; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import de.thedevstack.conversationsplus.ConversationsPlusPreferences; -import de.thedevstack.conversationsplus.http.HttpConnectionManager; -import de.thedevstack.conversationsplus.http.HttpDownloadConnection; -import de.thedevstack.conversationsplus.services.filetransfer.http.delete.DeleteRemoteFileService; -import de.thedevstack.conversationsplus.ui.dialogs.SimpleConfirmDialog; -import de.thedevstack.conversationsplus.ui.dialogs.MessageDetailsDialog; -import de.thedevstack.conversationsplus.Config; -import de.thedevstack.conversationsplus.R; -import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; -import de.thedevstack.conversationsplus.entities.Account; -import de.thedevstack.conversationsplus.entities.Contact; -import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.DownloadableFile; -import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.entities.MucOptions; -import de.thedevstack.conversationsplus.entities.Presence; -import de.thedevstack.conversationsplus.entities.Transferable; -import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; -import de.thedevstack.conversationsplus.persistance.FileBackend; -import de.thedevstack.conversationsplus.services.XmppConnectionService; -import de.thedevstack.conversationsplus.ui.XmppActivity.OnPresenceSelected; -import de.thedevstack.conversationsplus.ui.XmppActivity.OnValueEdited; -import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter; -import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter.OnContactPictureClicked; -import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter.OnContactPictureLongClicked; -import de.thedevstack.conversationsplus.ui.listeners.ConversationSwipeRefreshListener; -import de.thedevstack.conversationsplus.ui.listeners.DeleteFileCallback; -import de.thedevstack.conversationsplus.utils.AccountUtil; -import de.thedevstack.conversationsplus.utils.GeoHelper; -import de.thedevstack.conversationsplus.utils.MessageUtil; -import de.thedevstack.conversationsplus.utils.UIHelper; -import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import github.ankushsachdeva.emojicon.EmojiconGridView; -import github.ankushsachdeva.emojicon.EmojiconsPopup; -import github.ankushsachdeva.emojicon.emoji.Emojicon; - -public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener { - - protected Conversation conversation; - private OnClickListener leaveMuc = new OnClickListener() { - - @Override - public void onClick(View v) { - activity.endConversation(conversation); - } - }; - private OnClickListener joinMuc = new OnClickListener() { - - @Override - public void onClick(View v) { - activity.xmppConnectionService.joinMuc(conversation); - } - }; - private OnClickListener enterPassword = new OnClickListener() { - - @Override - public void onClick(View v) { - MucOptions muc = conversation.getMucOptions(); - String password = muc.getPassword(); - if (password == null) { - password = ""; - } - activity.quickPasswordEdit(password, new OnValueEdited() { - - @Override - public void onValueEdited(String value) { - activity.xmppConnectionService.providePasswordForMuc( - conversation, value); - } - }); - } - }; - protected ListView messagesView; - protected SwipyRefreshLayout swipeLayout; - final protected List<Message> messageList = new ArrayList<>(); - protected MessageAdapter messageListAdapter; - private EditMessage mEditMessage; - private ImageButton mSendButton; - private ImageView mEmojButton; - private View mRootView; - private EmojiconsPopup mEmojPopup; - private RelativeLayout snackbar; - private TextView snackbarMessage; - private TextView snackbarAction; - private boolean messagesLoaded = true; - private Toast messageLoaderToast; - private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0; - private final int KEYCHAIN_UNLOCK_REQUIRED = 1; - private final int KEYCHAIN_UNLOCK_PENDING = 2; - private int keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - protected OnClickListener clickToDecryptListener = new OnClickListener() { - - @Override - public void onClick(View v) { - if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED - && activity.hasPgp() && !conversation.getAccount().getPgpDecryptionService().isRunning()) { - keychainUnlock = KEYCHAIN_UNLOCK_PENDING; - updateSnackBar(conversation); - Message message = getLastPgpDecryptableMessage(); - if (message != null) { - activity.xmppConnectionService.getPgpEngine().decrypt(message, new UiCallback<Message>() { - @Override - public void success(Message object) { - conversation.getAccount().getPgpDecryptionService().onKeychainUnlocked(); - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - } - - @Override - public void error(int errorCode, Message object) { - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - } - - @Override - public void userInputRequried(PendingIntent pi, Message object) { - try { - activity.startIntentSenderForResult(pi.getIntentSender(), - ConversationActivity.REQUEST_DECRYPT_PGP, null, 0, 0, 0); - } catch (SendIntentException e) { - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - updatePgpMessages(); - } - } - }); - } - } else { - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - updatePgpMessages(); - } - } - }; - protected OnClickListener clickToVerify = new OnClickListener() { - - @Override - public void onClick(View v) { - activity.verifyOtrSessionDialog(conversation, v); - } - }; - private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() { - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - if (actionId == EditorInfo.IME_ACTION_SEND) { - InputMethodManager imm = (InputMethodManager) v.getContext() - .getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm.isFullscreenMode()) { - imm.hideSoftInputFromWindow(v.getWindowToken(), 0); - } - sendMessage(); - return true; - } else { - return false; - } - } - }; - private OnClickListener mSendButtonListener = new OnClickListener() { - - @Override - public void onClick(View v) { - Object tag = v.getTag(); - if (tag instanceof SendButtonAction) { - SendButtonAction action = (SendButtonAction) tag; - switch (action) { - case TAKE_PHOTO: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO); - break; - case SEND_LOCATION: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION); - break; - case RECORD_VOICE: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE); - break; - case CHOOSE_PICTURE: - activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case CANCEL: - if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { - conversation.setNextCounterpart(null); - updateChatMsgHint(); - updateSendButton(); - } - break; - default: - sendMessage(); - } - } else { - sendMessage(); - } - } - }; - private OnClickListener clickToMuc = new OnClickListener() { - - @Override - public void onClick(View v) { - Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); - } - }; - private ConversationActivity activity; - private Message selectedMessage; - - public void setMessagesLoaded() { - this.messagesLoaded = true; - } - - private void sendMessage() { - final String body = mEditMessage.getText().toString(); - if (body.length() == 0 || this.conversation == null) { - return; - } - Message message = new Message(conversation, body, conversation.getNextEncryption()); - if (conversation.getMode() == Conversation.MODE_MULTI) { - if (conversation.getNextCounterpart() != null) { - message.setCounterpart(conversation.getNextCounterpart()); - message.setType(Message.TYPE_PRIVATE); - } - } - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_OTR: - sendOtrMessage(message); - break; - case Message.ENCRYPTION_PGP: - sendPgpMessage(message); - break; - case Message.ENCRYPTION_AXOLOTL: - if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { - sendAxolotlMessage(message); - } - break; - default: - sendPlainTextMessage(message); - } - } - - public void updateChatMsgHint() { - final boolean multi = conversation.getMode() == Conversation.MODE_MULTI; - if (multi && conversation.getNextCounterpart() != null) { - this.mEditMessage.setHint(getString( - R.string.send_private_message_to, - conversation.getNextCounterpart().getResourcepart())); - } else if (multi && !conversation.getMucOptions().participating()) { - this.mEditMessage.setHint(R.string.you_are_not_participating); - } else { - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_NONE: - mEditMessage - .setHint(getString(R.string.send_unencrypted_message)); - break; - case Message.ENCRYPTION_OTR: - mEditMessage.setHint(getString(R.string.send_otr_message)); - break; - case Message.ENCRYPTION_AXOLOTL: - AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); - if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) { - mEditMessage.setHint(getString(R.string.send_omemo_x509_message)); - } else { - mEditMessage.setHint(getString(R.string.send_omemo_message)); - } - break; - case Message.ENCRYPTION_PGP: - mEditMessage.setHint(getString(R.string.send_pgp_message)); - break; - default: - break; - } - getActivity().invalidateOptionsMenu(); - } - } - - public void setupIme() { - if (activity == null) { - return; - } else if (ConversationsPlusPreferences.displayEnterKey() && ConversationsPlusPreferences.enterIsSend()) { - mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE)); - mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); - } else if (ConversationsPlusPreferences.displayEnterKey()) { - mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); - } else { - mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE); - } - } - - @Override - public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.fragment_conversation, container, false); - view.setOnClickListener(null); - mEditMessage = (EditMessage) view.findViewById(R.id.textinput); - mEditMessage.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (activity != null) { - activity.hideConversationsOverview(); - } - } - }); - mEditMessage.setOnEditorActionListener(mEditorActionListener); - - // Start of emojicon - mEmojButton = (ImageView) view.findViewById(R.id.emoji_btn); - mRootView = view.findViewById(R.id.textsend); - - // Give the topmost view of your activity layout hierarchy. This will be used to measure soft keyboard height - mEmojPopup = new EmojiconsPopup(mRootView, this.getActivity()); - - //Will automatically set size according to the soft keyboard size - mEmojPopup.setSizeForSoftKeyboard(); - - //If the emoji popup is dismissed, change emojiButton to smiley icon - mEmojPopup.setOnDismissListener(new PopupWindow.OnDismissListener() { - - @Override - public void onDismiss() { - changeEmojiKeyboardIcon(mEmojButton, R.drawable.smiley); - } - }); - - //If the text keyboard closes, also dismiss the emoji popup - mEmojPopup.setOnSoftKeyboardOpenCloseListener(new EmojiconsPopup.OnSoftKeyboardOpenCloseListener() { - - @Override - public void onKeyboardOpen(int keyBoardHeight) { - - } - - @Override - public void onKeyboardClose() { - if (mEmojPopup.isShowing()) - mEmojPopup.dismiss(); - } - }); - - //On emoji clicked, add it to edittext - mEmojPopup.setOnEmojiconClickedListener(new EmojiconGridView.OnEmojiconClickedListener() { - - @Override - public void onEmojiconClicked(Emojicon emojicon) { - if (mEditMessage == null || emojicon == null) { - return; - } - - int start = mEditMessage.getSelectionStart(); - int end = mEditMessage.getSelectionEnd(); - if (start < 0) { - mEditMessage.append(emojicon.getEmoji()); - } else { - mEditMessage.getText().replace(Math.min(start, end), - Math.max(start, end), emojicon.getEmoji(), 0, - emojicon.getEmoji().length()); - } - } - }); - - //On backspace clicked, emulate the KEYCODE_DEL key event - mEmojPopup.setOnEmojiconBackspaceClickedListener(new EmojiconsPopup.OnEmojiconBackspaceClickedListener() { - - @Override - public void onEmojiconBackspaceClicked(View v) { - KeyEvent event = new KeyEvent( - 0, 0, 0, KeyEvent.KEYCODE_DEL, 0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL); - mEditMessage.dispatchKeyEvent(event); - } - }); - - // To toggle between text keyboard and emoji keyboard keyboard(Popup) - mEmojButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - - //If popup is not showing => emoji keyboard is not visible, we need to show it - if(!mEmojPopup.isShowing()){ - - //If keyboard is visible, simply show the emoji popup - if(mEmojPopup.isKeyBoardOpen()){ - mEmojPopup.showAtBottom(); - changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard); - } - - //else, open the text keyboard first and immediately after that show the emoji popup - else{ - mEditMessage.setFocusableInTouchMode(true); - mEditMessage.requestFocus(); - mEmojPopup.showAtBottomPending(); - final InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(mEditMessage, InputMethodManager.SHOW_IMPLICIT); - changeEmojiKeyboardIcon(mEmojButton, R.drawable.ic_action_keyboard); - } - } - - //If popup is showing, simply dismiss it to show the undelying text keyboard - else{ - mEmojPopup.dismiss(); - } - } - }); - - // End of emojicon - - mSendButton = (ImageButton) view.findViewById(R.id.textSendButton); - mSendButton.setOnClickListener(this.mSendButtonListener); - - snackbar = (RelativeLayout) view.findViewById(R.id.snackbar); - snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message); - snackbarAction = (TextView) view.findViewById(R.id.snackbar_action); - - messagesView = (ListView) view.findViewById(R.id.messages_view); - messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); - messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList); - messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() { - - @Override - public void onContactPictureClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart(); - if (!message.getConversation().getMucOptions().isUserInRoom(user)) { - Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user),Toast.LENGTH_SHORT).show(); - } - highlightInConference(user); - } - } else { - activity.switchToContactDetails(message.getContact(), message.getFingerprint()); - } - } else { - Account account = message.getConversation().getAccount(); - Intent intent = new Intent(activity, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().toBareJid().toString()); - intent.putExtra("fingerprint", message.getFingerprint()); - startActivity(intent); - } - } - }); - messageListAdapter - .setOnContactPictureLongClicked(new OnContactPictureLongClicked() { - - @Override - public void onContactPictureLongClicked(Message message) { - if (message.getStatus() <= Message.STATUS_RECEIVED) { - if (message.getConversation().getMode() == Conversation.MODE_MULTI) { - if (message.getCounterpart() != null) { - String user = message.getCounterpart().getResourcepart(); - if (user != null) { - if (message.getConversation().getMucOptions().isUserInRoom(user)) { - privateMessageWith(message.getCounterpart()); - } else { - Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user), Toast.LENGTH_SHORT).show(); - } - } - } - } - } else { - activity.showQrCode(); - } - } - }); - messagesView.setAdapter(messageListAdapter); - - registerForContextMenu(messagesView); - - // Start of swipe refresh - // New Swipe refresh - swipeLayout = (SwipyRefreshLayout) view.findViewById(R.id.swipe_refresh_container); - swipeLayout.setOnRefreshListener(new ConversationSwipeRefreshListener(messageList, swipeLayout, this, messagesView, messageListAdapter)); - // End of swipe refresh - - return view; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - synchronized (this.messageList) { - super.onCreateContextMenu(menu, v, menuInfo); - AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; - this.selectedMessage = this.messageList.get(acmi.position); - populateContextMenu(menu); - } - } - - private void populateContextMenu(ContextMenu menu) { - final Message m = this.selectedMessage; - final Transferable t = m.getTransferable(); - if (m.getType() != Message.TYPE_STATUS) { - final boolean treatAsFile = m.getType() != Message.TYPE_TEXT - && m.getType() != Message.TYPE_PRIVATE - && t == null; - activity.getMenuInflater().inflate(R.menu.message_context, menu); - menu.setHeaderTitle(R.string.message_options); - MenuItem copyText = menu.findItem(R.id.copy_text); - MenuItem retryDecryption = menu.findItem(R.id.retry_decryption); - MenuItem shareWith = menu.findItem(R.id.share_with); - MenuItem sendAgain = menu.findItem(R.id.send_again); - MenuItem copyUrl = menu.findItem(R.id.copy_url); - MenuItem downloadFile = menu.findItem(R.id.download_file); - MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission); - MenuItem deleteFile = menu.findItem(R.id.delete_file); - if (!treatAsFile - && !GeoHelper.isGeoUri(m.getBody()) - && m.treatAsDownloadable() != Message.Decision.MUST) { - copyText.setVisible(true); - } - if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - retryDecryption.setVisible(true); - } - if (treatAsFile || (GeoHelper.isGeoUri(m.getBody()))) { - shareWith.setVisible(true); - } - if (m.getStatus() == Message.STATUS_SEND_FAILED) { - sendAgain.setVisible(true); - } - if (m.hasFileOnRemoteHost() - || GeoHelper.isGeoUri(m.getBody()) - || m.treatAsDownloadable() == Message.Decision.MUST - || (t != null && t instanceof HttpDownloadConnection)) { - copyUrl.setVisible(true); - } - if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER) - || (t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){ - downloadFile.setVisible(true); - downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m))); - } - if ((t != null && !(t instanceof TransferablePlaceholder)) - || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING - || m.getStatus() == Message.STATUS_OFFERED))) { - cancelTransmission.setVisible(true); - } - if (treatAsFile) { - deleteFile.setVisible(true); - deleteFile.setTitle(activity.getString(R.string.delete_x_file,UIHelper.getFileDescriptionString(activity, m))); - } - if (m.isHttpUploaded() && MessageUtil.isMessageSent(m) && AccountUtil.isFileTransferHttpAvailable(m.getConversation().getAccount())) { - MenuItem deleteRemoteFile = menu.findItem(R.id.msg_ctx_menu_delete_remote_file); - deleteRemoteFile.setVisible(true); - } - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.msg_ctx_menu_delete_remote_file: - new SimpleConfirmDialog(getActivity(), R.string.cplus_are_you_sure, new DeleteRemoteFileService(selectedMessage)).show(); - return true; - case R.id.msg_ctx_mnu_details: - new MessageDetailsDialog(getActivity(), selectedMessage).show(); - return true; - case R.id.share_with: - shareWith(selectedMessage); - return true; - case R.id.copy_text: - copyText(selectedMessage); - return true; - case R.id.send_again: - resendMessage(selectedMessage); - return true; - case R.id.copy_url: - copyUrl(selectedMessage); - return true; - case R.id.download_file: - downloadFile(selectedMessage); - return true; - case R.id.cancel_transmission: - cancelTransmission(selectedMessage); - return true; - case R.id.retry_decryption: - retryDecryption(selectedMessage); - return true; - case R.id.delete_file: - new SimpleConfirmDialog(getActivity(), R.string.cplus_are_you_sure, new DeleteFileCallback(selectedMessage)).show(); - return true; - default: - return super.onContextItemSelected(item); - } - } - - private void shareWith(Message message) { - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - if (GeoHelper.isGeoUri(message.getBody())) { - shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody()); - shareIntent.setType("text/plain"); - } else { - shareIntent.putExtra(Intent.EXTRA_STREAM, - FileBackend.getJingleFileUri(message)); - shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - String mime = message.getMimeType(); - if (mime == null) { - mime = "*/*"; - } - shareIntent.setType(mime); - } - try { - activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with))); - } catch (ActivityNotFoundException e) { - //This should happen only on faulty androids because normally chooser is always available - Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show(); - } - } - - private void copyText(Message message) { - if (activity.copyTextToClipboard(message.getBody(), - R.string.message_text)) { - Toast.makeText(activity, R.string.message_copied_to_clipboard, - Toast.LENGTH_SHORT).show(); - } - } - - private void resendMessage(Message message) { - if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { - DownloadableFile file = FileBackend.getFile(message); - if (!file.exists()) { - Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show(); - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - activity.xmppConnectionService.updateConversationUi(); - return; - } - } - activity.xmppConnectionService.resendFailedMessages(message); - } - - private void copyUrl(Message message) { - final String url; - final int resId; - if (GeoHelper.isGeoUri(message.getBody())) { - resId = R.string.location; - url = message.getBody(); - } else if (message.hasFileOnRemoteHost()) { - resId = R.string.file_url; - url = message.getFileParams().getUrl(); - } else { - url = message.getBody(); - resId = R.string.file_url; - } - if (activity.copyTextToClipboard(url, resId)) { - Toast.makeText(activity, R.string.url_copied_to_clipboard, - Toast.LENGTH_SHORT).show(); - } - } - - private void downloadFile(Message message) { - HttpDownloadConnection downloadConnection = HttpConnectionManager.createNewDownloadConnection(message, true); - if (null == downloadConnection) { - Toast.makeText(activity, R.string.file_not_on_remote_host, Toast.LENGTH_LONG).show(); - } - } - - private void cancelTransmission(Message message) { - Transferable transferable = message.getTransferable(); - if (transferable != null) { - transferable.cancel(); - } else { - MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED); - } - } - - private void retryDecryption(Message message) { - message.setEncryption(Message.ENCRYPTION_PGP); - activity.xmppConnectionService.updateConversationUi(); - conversation.getAccount().getPgpDecryptionService().add(message); - } - - protected void privateMessageWith(final Jid counterpart) { - this.mEditMessage.setText(""); - this.conversation.setNextCounterpart(counterpart); - updateChatMsgHint(); - updateSendButton(); - } - - protected void highlightInConference(String nick) { - String oldString = mEditMessage.getText().toString(); - if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) { - mEditMessage.getText().insert(0, nick + ": "); - } else { - if (mEditMessage.getText().charAt( - mEditMessage.getSelectionStart() - 1) != ' ') { - nick = " " + nick; - } - mEditMessage.getText().insert(mEditMessage.getSelectionStart(), - nick + " "); - } - } - - @Override - public void onStop() { - super.onStop(); - if (this.conversation != null) { - final String msg = mEditMessage.getText().toString(); - this.conversation.setNextMessage(msg); - updateChatState(this.conversation, msg); - } - } - - private void updateChatState(final Conversation conversation, final String msg) { - ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED; - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) { - activity.xmppConnectionService.sendChatState(conversation); - } - } - - public void reInit(Conversation conversation) { - if (conversation == null) { - return; - } - this.activity = (ConversationActivity) getActivity(); - setupIme(); - if (this.conversation != null) { - final String msg = mEditMessage.getText().toString(); - this.conversation.setNextMessage(msg); - if (this.conversation != conversation) { - updateChatState(this.conversation, msg); - } - this.conversation.trim(); - } - - this.keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - this.conversation = conversation; - boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating(); - this.mEditMessage.setEnabled(canWrite); - this.mSendButton.setEnabled(canWrite); - this.mEditMessage.setKeyboardListener(null); - this.mEditMessage.setText(""); - this.mEditMessage.append(this.conversation.getNextMessage()); - this.mEditMessage.setKeyboardListener(this); - this.messagesView.setAdapter(messageListAdapter); - updateMessages(); - this.messagesLoaded = true; - int size = this.messageList.size(); - if (size > 0) { - messagesView.setSelection(size - 1); - } - swipeLayout.setRefreshing(false); - } - - private OnClickListener mEnableAccountListener = new OnClickListener() { - @Override - public void onClick(View v) { - final Account account = conversation == null ? null : conversation.getAccount(); - if (account != null) { - account.setOption(Account.OPTION_DISABLED, false); - activity.xmppConnectionService.updateAccount(account); - } - } - }; - - private OnClickListener mUnblockClickListener = new OnClickListener() { - @Override - public void onClick(final View v) { - v.post(new Runnable() { - @Override - public void run() { - v.setVisibility(View.INVISIBLE); - } - }); - if (conversation.isDomainBlocked()) { - BlockContactDialog.show(activity, activity.xmppConnectionService, conversation); - } else { - activity.unblockConversation(conversation); - } - } - }; - - private OnClickListener mAddBackClickListener = new OnClickListener() { - - @Override - public void onClick(View v) { - final Contact contact = conversation == null ? null : conversation.getContact(); - if (contact != null) { - activity.xmppConnectionService.createContact(contact); - activity.switchToContactDetails(contact); - } - } - }; - - private OnClickListener mAnswerSmpClickListener = new OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(activity, VerifyOTRActivity.class); - intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT); - intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString()); - intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString()); - intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION); - startActivity(intent); - } - }; - - private void updateSnackBar(final Conversation conversation) { - final Account account = conversation.getAccount(); - final Contact contact = conversation.getContact(); - final int mode = conversation.getMode(); - if (account.getStatus() == Account.State.DISABLED) { - showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener); - } else if (conversation.isBlocked()) { - showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener); - } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener); - } else if (mode == Conversation.MODE_MULTI - && !conversation.getMucOptions().online() - && account.getStatus() == Account.State.ONLINE) { - switch (conversation.getMucOptions().getError()) { - case NICK_IN_USE: - showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc); - break; - case NO_RESPONSE: - showSnackbar(R.string.joining_conference, 0, null); - break; - case PASSWORD_REQUIRED: - showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword); - break; - case BANNED: - showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc); - break; - case MEMBERS_ONLY: - showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc); - break; - case KICKED: - showSnackbar(R.string.conference_kicked, R.string.join, joinMuc); - break; - case UNKNOWN: - showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc); - break; - case SHUTDOWN: - showSnackbar(R.string.conference_shutdown, R.string.join, joinMuc); - break; - default: - break; - } - } else if (keychainUnlock == KEYCHAIN_UNLOCK_REQUIRED) { - showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener); - } else if (mode == Conversation.MODE_SINGLE - && conversation.smpRequested()) { - showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener); - } else if (mode == Conversation.MODE_SINGLE - && conversation.hasValidOtrSession() - && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) - && (!conversation.isOtrFingerprintVerified())) { - showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify); - } else { - hideSnackbar(); - } - } - - public void updateMessages() { - synchronized (this.messageList) { - if (getView() == null) { - return; - } - final ConversationActivity activity = (ConversationActivity) getActivity(); - if (this.conversation != null) { - conversation.populateWithMessages(ConversationFragment.this.messageList); - updatePgpMessages(); - updateSnackBar(conversation); - updateStatusMessages(); - this.messageListAdapter.notifyDataSetChanged(); - updateChatMsgHint(); - if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) { - activity.sendReadMarkerIfNecessary(conversation); - } - this.updateSendButton(); - } - } - } - - public void updatePgpMessages() { - if (keychainUnlock != KEYCHAIN_UNLOCK_PENDING) { - if (getLastPgpDecryptableMessage() != null - && !conversation.getAccount().getPgpDecryptionService().isRunning()) { - keychainUnlock = KEYCHAIN_UNLOCK_REQUIRED; - } else { - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - } - } - } - - @Nullable - private Message getLastPgpDecryptableMessage() { - for (final Message message : this.messageList) { - if (message.getEncryption() == Message.ENCRYPTION_PGP - && (message.getStatus() == Message.STATUS_RECEIVED || message.getStatus() >= Message.STATUS_SEND) - && message.getTransferable() == null) { - return message; - } - } - return null; - } - - private void messageSent() { - int size = this.messageList.size(); - messagesView.setSelection(size - 1); - mEditMessage.setText(""); - updateChatMsgHint(); - } - - public void setFocusOnInputField() { - mEditMessage.requestFocus(); - } - - enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE} - - private int getSendButtonImageResource(SendButtonAction action, Presence.Status status) { - switch (action) { - case TEXT: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_text_online; - case AWAY: - return R.drawable.ic_send_text_away; - case XA: - case DND: - return R.drawable.ic_send_text_dnd; - default: - return R.drawable.ic_send_text_offline; - } - case TAKE_PHOTO: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_photo_online; - case AWAY: - return R.drawable.ic_send_photo_away; - case XA: - case DND: - return R.drawable.ic_send_photo_dnd; - default: - return R.drawable.ic_send_photo_offline; - } - case RECORD_VOICE: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_voice_online; - case AWAY: - return R.drawable.ic_send_voice_away; - case XA: - case DND: - return R.drawable.ic_send_voice_dnd; - default: - return R.drawable.ic_send_voice_offline; - } - case SEND_LOCATION: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_location_online; - case AWAY: - return R.drawable.ic_send_location_away; - case XA: - case DND: - return R.drawable.ic_send_location_dnd; - default: - return R.drawable.ic_send_location_offline; - } - case CANCEL: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_cancel_online; - case AWAY: - return R.drawable.ic_send_cancel_away; - case XA: - case DND: - return R.drawable.ic_send_cancel_dnd; - default: - return R.drawable.ic_send_cancel_offline; - } - case CHOOSE_PICTURE: - switch (status) { - case CHAT: - case ONLINE: - return R.drawable.ic_send_picture_online; - case AWAY: - return R.drawable.ic_send_picture_away; - case XA: - case DND: - return R.drawable.ic_send_picture_dnd; - default: - return R.drawable.ic_send_picture_offline; - } - } - return R.drawable.ic_send_text_offline; - } - - public void updateSendButton() { - final Conversation c = this.conversation; - final SendButtonAction action; - final Presence.Status status; - final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString(); - final boolean empty = text.length() == 0; - final boolean conference = c.getMode() == Conversation.MODE_MULTI; - if (conference && !AccountUtil.isHttpUploadAvailable(c.getAccount())) { - if (empty && c.getNextCounterpart() != null) { - action = SendButtonAction.CANCEL; - } else { - action = SendButtonAction.TEXT; - } - } else { - if (empty) { - if (conference && c.getNextCounterpart() != null) { - action = SendButtonAction.CANCEL; - } else { - String setting = ConversationsPlusPreferences.quickAction(); - if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) { - setting = "location"; - } else if (setting.equals("recent")) { - setting = ConversationsPlusPreferences.recentlyUsedQuickAction(); - } - switch (setting) { - case "photo": - action = SendButtonAction.TAKE_PHOTO; - break; - case "location": - action = SendButtonAction.SEND_LOCATION; - break; - case "voice": - action = SendButtonAction.RECORD_VOICE; - break; - case "picture": - action = SendButtonAction.CHOOSE_PICTURE; - break; - default: - action = SendButtonAction.TEXT; - break; - } - } - } else { - action = SendButtonAction.TEXT; - } - } - if (ConversationsPlusPreferences.sendButtonStatus() && c != null - && c.getAccount().getStatus() == Account.State.ONLINE) { - if (c.getMode() == Conversation.MODE_SINGLE) { - status = c.getContact().getMostAvailableStatus(); - } else { - status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE; - } - } else { - status = Presence.Status.OFFLINE; - } - this.mSendButton.setTag(action); - this.mSendButton.setImageResource(getSendButtonImageResource(action, status)); - } - - public void updateStatusMessages() { - synchronized (this.messageList) { - if (conversation.getMode() == Conversation.MODE_SINGLE) { - ChatState state = conversation.getIncomingChatState(); - if (state == ChatState.COMPOSING) { - this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName()))); - } else if (state == ChatState.PAUSED) { - this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName()))); - } else { - for (int i = this.messageList.size() - 1; i >= 0; --i) { - if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) { - return; - } else { - if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) { - this.messageList.add(i + 1, - Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName()))); - return; - } - } - } - } - } - } - } - - protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) { - snackbar.setVisibility(View.VISIBLE); - snackbar.setOnClickListener(null); - snackbarMessage.setText(message); - snackbarMessage.setOnClickListener(null); - snackbarAction.setVisibility(clickListener == null ? View.GONE : View.VISIBLE); - if (action != 0) { - snackbarAction.setText(action); - } - snackbarAction.setOnClickListener(clickListener); - } - - protected void hideSnackbar() { - snackbar.setVisibility(View.GONE); - } - - protected void sendPlainTextMessage(Message message) { - ConversationActivity activity = (ConversationActivity) getActivity(); - activity.xmppConnectionService.sendMessage(message); - messageSent(); - } - - protected void sendPgpMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - final Contact contact = message.getConversation().getContact(); - if (!activity.hasPgp()) { - activity.showInstallPgpDialog(); - return; - } - if (conversation.getAccount().getPgpSignature() == null) { - activity.announcePgp(conversation.getAccount(), conversation); - return; - } - if (conversation.getMode() == Conversation.MODE_SINGLE) { - if (contact.getPgpKeyId() != 0) { - xmppService.getPgpEngine().hasKey(contact, - new UiCallback<Contact>() { - - @Override - public void userInputRequried(PendingIntent pi, - Contact contact) { - activity.runIntent( - pi, - ConversationActivity.REQUEST_ENCRYPT_MESSAGE); - } - - @Override - public void success(Contact contact) { - messageSent(); - activity.encryptTextMessage(message); - } - - @Override - public void error(int error, Contact contact) { - System.out.println(); - } - }); - - } else { - showNoPGPKeyDialog(false, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - xmppService.databaseBackend - .updateConversation(conversation); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.sendMessage(message); - messageSent(); - } - }); - } - } else { - if (conversation.getMucOptions().pgpKeysInUse()) { - if (!conversation.getMucOptions().everybodyHasKeys()) { - Toast warning = Toast - .makeText(getActivity(), - R.string.missing_public_keys, - Toast.LENGTH_LONG); - warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0); - warning.show(); - } - activity.encryptTextMessage(message); - messageSent(); - } else { - showNoPGPKeyDialog(true, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - conversation - .setNextEncryption(Message.ENCRYPTION_NONE); - message.setEncryption(Message.ENCRYPTION_NONE); - xmppService.databaseBackend - .updateConversation(conversation); - xmppService.sendMessage(message); - messageSent(); - } - }); - } - } - } - - public void showNoPGPKeyDialog(boolean plural, - DialogInterface.OnClickListener listener) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - if (plural) { - builder.setTitle(getString(R.string.no_pgp_keys)); - builder.setMessage(getText(R.string.contacts_have_no_pgp_keys)); - } else { - builder.setTitle(getString(R.string.no_pgp_key)); - builder.setMessage(getText(R.string.contact_has_no_pgp_key)); - } - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.send_unencrypted), - listener); - builder.create().show(); - } - - protected void sendAxolotlMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - xmppService.sendMessage(message); - messageSent(); - } - - protected void sendOtrMessage(final Message message) { - final ConversationActivity activity = (ConversationActivity) getActivity(); - final XmppConnectionService xmppService = activity.xmppConnectionService; - activity.selectPresence(message.getConversation(), - new OnPresenceSelected() { - - @Override - public void onPresenceSelected() { - message.setCounterpart(conversation.getNextCounterpart()); - xmppService.sendMessage(message); - messageSent(); - } - }); - } - - public void appendText(String text) { - if (text == null) { - return; - } - String previous = this.mEditMessage.getText().toString(); - if (previous.length() != 0 && !previous.endsWith(" ")) { - text = " " + text; - } - this.mEditMessage.append(text); - } - - @Override - public boolean onEnterPressed() { - if (ConversationsPlusPreferences.enterIsSend()) { - sendMessage(); - return true; - } else { - return false; - } - } - - @Override - public void onTypingStarted() { - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) { - activity.xmppConnectionService.sendChatState(conversation); - } - activity.hideConversationsOverview(); - updateSendButton(); - } - - @Override - public void onTypingStopped() { - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) { - activity.xmppConnectionService.sendChatState(conversation); - } - } - - @Override - public void onTextDeleted() { - Account.State status = conversation.getAccount().getStatus(); - if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) { - activity.xmppConnectionService.sendChatState(conversation); - } - updateSendButton(); - } - - private int completionIndex = 0; - private int lastCompletionLength = 0; - private String incomplete; - private int lastCompletionCursor; - private boolean firstWord = false; - - @Override - public boolean onTabPressed(boolean repeated) { - if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) { - return false; - } - if (repeated) { - completionIndex++; - } else { - lastCompletionLength = 0; - completionIndex = 0; - final String content = mEditMessage.getText().toString(); - lastCompletionCursor = mEditMessage.getSelectionEnd(); - int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ",lastCompletionCursor-1) + 1 : 0; - firstWord = start == 0; - incomplete = content.substring(start,lastCompletionCursor); - } - List<String> completions = new ArrayList<>(); - for(MucOptions.User user : conversation.getMucOptions().getUsers()) { - if (user.getName().startsWith(incomplete)) { - completions.add(user.getName()+(firstWord ? ": " : " ")); - } - } - Collections.sort(completions); - if (completions.size() > completionIndex) { - String completion = completions.get(completionIndex).substring(incomplete.length()); - mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); - mEditMessage.getEditableText().insert(lastCompletionCursor, completion); - lastCompletionLength = completion.length(); - } else { - completionIndex = -1; - mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength); - lastCompletionLength = 0; - } - return true; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, - final Intent data) { - if (resultCode == Activity.RESULT_OK) { - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - activity.getSelectedConversation().getAccount().getPgpDecryptionService().onKeychainUnlocked(); - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - updatePgpMessages(); - } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { - final String body = mEditMessage.getText().toString(); - Message message = new Message(conversation, body, conversation.getNextEncryption()); - sendAxolotlMessage(message); - } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { - int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); - activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); - } - } else { - if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) { - keychainUnlock = KEYCHAIN_UNLOCK_NOT_REQUIRED; - updatePgpMessages(); - } - } - } - - private void changeEmojiKeyboardIcon(ImageView iconToBeChanged, int drawableResourceId){ - iconToBeChanged.setImageResource(drawableResourceId); - } - -} |