Java tutorial
/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.waz.zclient.pages.main.conversation; import android.Manifest; import android.annotation.SuppressLint; import android.app.Dialog; import android.app.ProgressDialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Color; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; import android.support.v4.app.ShareCompat; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.text.format.Formatter; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.Animation; import android.widget.AbsListView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.waz.api.Asset; import com.waz.api.AssetFactory; import com.waz.api.AssetForUpload; import com.waz.api.AudioAssetForUpload; import com.waz.api.AudioEffect; import com.waz.api.ConversationsList; import com.waz.api.ErrorsList; import com.waz.api.IConversation; import com.waz.api.ImageAsset; import com.waz.api.ImageAssetFactory; import com.waz.api.InputStateIndicator; import com.waz.api.Message; import com.waz.api.MessageContent; import com.waz.api.MessagesList; import com.waz.api.NetworkMode; import com.waz.api.OtrClient; import com.waz.api.SyncIndicator; import com.waz.api.SyncState; import com.waz.api.UpdateListener; import com.waz.api.User; import com.waz.api.UsersList; import com.waz.api.Verification; import com.waz.zclient.BaseScalaActivity; import com.waz.zclient.BuildConfig; import com.waz.zclient.OnBackPressedListener; import com.waz.zclient.R; import com.waz.zclient.camera.controllers.GlobalCameraController; import com.waz.zclient.controllers.IControllerFactory; import com.waz.zclient.controllers.accentcolor.AccentColorObserver; import com.waz.zclient.controllers.calling.CallingObserver; import com.waz.zclient.controllers.confirmation.ConfirmationCallback; import com.waz.zclient.controllers.confirmation.ConfirmationRequest; import com.waz.zclient.controllers.confirmation.IConfirmationController; import com.waz.zclient.controllers.currentfocus.IFocusController; import com.waz.zclient.controllers.drawing.IDrawingController; import com.waz.zclient.controllers.giphy.GiphyObserver; import com.waz.zclient.controllers.globallayout.KeyboardVisibilityObserver; import com.waz.zclient.controllers.mentioning.MentioningObserver; import com.waz.zclient.controllers.navigation.NavigationControllerObserver; import com.waz.zclient.controllers.navigation.Page; import com.waz.zclient.controllers.navigation.PagerControllerObserver; import com.waz.zclient.controllers.permission.RequestPermissionsObserver; import com.waz.zclient.controllers.singleimage.SingleImageObserver; import com.waz.zclient.controllers.streammediaplayer.StreamMediaBarObserver; import com.waz.zclient.controllers.tracking.events.conversation.CopiedMessageEvent; import com.waz.zclient.controllers.tracking.events.conversation.DeletedMessageEvent; import com.waz.zclient.controllers.tracking.events.conversation.EditedMessageEvent; import com.waz.zclient.controllers.tracking.events.conversation.ForwardedMessageEvent; import com.waz.zclient.controllers.tracking.events.conversation.OpenedMessageActionEvent; import com.waz.zclient.controllers.tracking.events.conversation.ReactedToMessageEvent; import com.waz.zclient.controllers.tracking.events.navigation.OpenedMoreActionsEvent; import com.waz.zclient.controllers.userpreferences.IUserPreferencesController; import com.waz.zclient.core.api.scala.ModelObserver; import com.waz.zclient.core.controllers.tracking.attributes.RangedAttribute; import com.waz.zclient.core.controllers.tracking.events.filetransfer.SavedFileEvent; import com.waz.zclient.core.controllers.tracking.events.filetransfer.SelectedTooLargeFileEvent; import com.waz.zclient.core.controllers.tracking.events.media.CancelledRecordingAudioMessageEvent; import com.waz.zclient.core.controllers.tracking.events.media.OpenedActionHintEvent; import com.waz.zclient.core.controllers.tracking.events.media.OpenedEmojiKeyboardEvent; import com.waz.zclient.core.controllers.tracking.events.media.OpenedMediaActionEvent; import com.waz.zclient.core.controllers.tracking.events.media.PreviewedAudioMessageEvent; import com.waz.zclient.core.controllers.tracking.events.media.SentPictureEvent; import com.waz.zclient.core.controllers.tracking.events.media.SentVideoMessageEvent; import com.waz.zclient.core.controllers.tracking.events.media.StartedRecordingAudioMessageEvent; import com.waz.zclient.core.stores.IStoreFactory; import com.waz.zclient.core.stores.conversation.ConversationChangeRequester; import com.waz.zclient.core.stores.conversation.ConversationStoreObserver; import com.waz.zclient.core.stores.inappnotification.InAppNotificationStoreObserver; import com.waz.zclient.core.stores.inappnotification.KnockingEvent; import com.waz.zclient.core.stores.network.DefaultNetworkAction; import com.waz.zclient.core.stores.participants.ParticipantsStoreObserver; import com.waz.zclient.notifications.controllers.ImageNotificationsController; import com.waz.zclient.pages.BaseFragment; import com.waz.zclient.pages.extendedcursor.ExtendedCursorContainer; import com.waz.zclient.pages.extendedcursor.emoji.EmojiKeyboardLayout; import com.waz.zclient.pages.extendedcursor.image.CursorImagesLayout; import com.waz.zclient.pages.extendedcursor.image.ImagePreviewLayout; import com.waz.zclient.pages.extendedcursor.voicefilter.VoiceFilterLayout; import com.waz.zclient.pages.main.calling.enums.VoiceBarAppearance; import com.waz.zclient.pages.main.conversation.views.TypingIndicatorView; import com.waz.zclient.pages.main.conversation.views.ExpandableView; import com.waz.zclient.pages.main.conversation.views.MessageBottomSheetDialog; import com.waz.zclient.pages.main.conversation.views.MessageViewsContainer; import com.waz.zclient.pages.main.conversation.views.header.StreamMediaPlayerBarFragment; import com.waz.zclient.pages.main.conversation.views.listview.ConversationListView; import com.waz.zclient.pages.main.conversation.views.listview.ConversationScrollListener; import com.waz.zclient.pages.main.conversation.views.row.message.MessageViewController; import com.waz.zclient.pages.main.conversation.views.row.message.views.ImageMessageViewController; import com.waz.zclient.pages.main.conversation.views.row.message.views.MediaPlayerViewController; import com.waz.zclient.pages.main.conversation.views.row.message.views.YouTubeMessageViewController; import com.waz.zclient.pages.main.conversationlist.ConversationListAnimation; import com.waz.zclient.pages.main.conversationpager.controller.SlidingPaneObserver; import com.waz.zclient.pages.main.onboarding.OnBoardingHintFragment; import com.waz.zclient.pages.main.onboarding.OnBoardingHintType; import com.waz.zclient.pages.main.pickuser.controller.IPickUserController; import com.waz.zclient.pages.main.profile.ZetaPreferencesActivity; import com.waz.zclient.pages.main.profile.camera.CameraContext; import com.waz.zclient.ui.animation.interpolators.penner.Expo; import com.waz.zclient.ui.audiomessage.AudioMessageRecordingView; import com.waz.zclient.ui.cursor.CursorCallback; import com.waz.zclient.ui.cursor.CursorLayout; import com.waz.zclient.ui.cursor.CursorMenuItem; import com.waz.zclient.ui.theme.ThemeUtils; import com.waz.zclient.ui.utils.KeyboardUtils; import com.waz.zclient.utils.AssetUtils; import com.waz.zclient.utils.Callback; import com.waz.zclient.utils.LayoutSpec; import com.waz.zclient.utils.OtrDestination; import com.waz.zclient.utils.PermissionUtils; import com.waz.zclient.utils.TrackingUtils; import com.waz.zclient.utils.ViewUtils; import com.waz.zclient.views.LoadingIndicatorView; import com.waz.zclient.views.MentioningFragment; import net.hockeyapp.android.ExceptionHandler; import timber.log.Timber; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class ConversationFragment extends BaseFragment<ConversationFragment.Container> implements ConversationStoreObserver, CallingObserver, OnBoardingHintFragment.Container, ConversationScrollListener.ScrolledToBottomListener, ConversationScrollListener.VisibleMessagesChangesListener, KeyboardVisibilityObserver, AccentColorObserver, StreamMediaPlayerBarFragment.Container, StreamMediaBarObserver, ParticipantsStoreObserver, InAppNotificationStoreObserver, MessageViewsContainer, NavigationControllerObserver, SlidingPaneObserver, SingleImageObserver, MentioningObserver, GiphyObserver, OnBackPressedListener, CursorCallback, AudioMessageRecordingView.Callback, RequestPermissionsObserver, ImagePreviewLayout.Callback, AssetIntentsManager.Callback, PagerControllerObserver, CursorImagesLayout.Callback, VoiceFilterLayout.Callback, EmojiKeyboardLayout.Callback, ExtendedCursorContainer.Callback, TypingIndicatorView.Callback { public static final String TAG = ConversationFragment.class.getName(); private static final String SAVED_STATE_PREVIEW = "SAVED_STATE_PREVIEW"; private static final int REQUEST_VIDEO_CAPTURE = 911; private static final int CAMERA_PERMISSION_REQUEST_ID = 21; private static final int BOTTOM_MENU_DISPLAY_DELAY_MS = 200; private static final String[] EXTENDED_CURSOR_PERMISSIONS = new String[] { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE }; private static final int OPEN_EXTENDED_CURSOR_IMAGES = 1254; private static final String[] FILE_SHARING_PERMISSION = new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }; private static final int FILE_SHARING_PERMISSION_REQUEST_ID = 179; private static final String[] AUDIO_PERMISSION = new String[] { android.Manifest.permission.RECORD_AUDIO }; private static final int AUDIO_PERMISSION_REQUEST_ID = 864; private static final int AUDIO_FILTER_PERMISSION_REQUEST_ID = 865; private static final String[] SAVE_IMAGE_PERMISSIONS = new String[] { android.Manifest.permission.WRITE_EXTERNAL_STORAGE }; private static final int SAVE_IMAGE_PERMISSION_REQUEST_ID = 6; private ConversationListView listView; private MessageAdapter messageAdapter; private MessageStreamManager messageStreamManager; private InputStateIndicator inputStateIndicator; private UpdateListener typingListener; private TypingIndicatorView typingIndicatorView; private LoadingIndicatorView conversationLoadingIndicatorViewView; private StreamMediaPlayerBarFragment streamMediaBarFragment; private FrameLayout invisibleFooter; private IConversation.Type toConversationType; private String expandedMessageId; private ExpandableView currentExpandableView; private String lastPingMessageId; private String lastHotPingMessageId; private Toolbar toolbar; private TextView toolbarTitle; private CursorLayout cursorLayout; private AudioMessageRecordingView audioMessageRecordingView; private ExtendedCursorContainer extendedCursorContainer; private List<Uri> sharingUris = new ArrayList<>(); private AssetIntentsManager assetIntentsManager; private ViewGroup containerPreview; private boolean isPreviewShown; private boolean isVideoMessageButtonClicked; private MessageBottomSheetDialog messageBottomSheetDialog; private ImageAsset imageAssetToSave; public static ConversationFragment newInstance() { return new ConversationFragment(); } private final ModelObserver<IConversation> conversationModelObserver = new ModelObserver<IConversation>() { @Override public void updated(IConversation model) { if (toolbar == null || toolbarTitle == null) { return; } toolbarTitle.setText(model.getName()); toolbar.getMenu().clear(); if (!model.isMemberOfConversation()) { return; } if (model.getType() == IConversation.Type.ONE_TO_ONE) { toolbar.inflateMenu(R.menu.conversation_header_menu_video); } else { toolbar.inflateMenu(R.menu.conversation_header_menu_audio); } } }; private final ModelObserver<MessagesList> messagesListModelObserver = new ModelObserver<MessagesList>() { @Override public void updated(MessagesList messagesList) { if (LayoutSpec.isPhone(getActivity()) && getControllerFactory().getNavigationController().getCurrentPage() != Page.MESSAGE_STREAM) { return; } showLoadingIndicator(messagesList); syncIndicatorModelObserver.setAndUpdate(messagesList.getSyncIndicator()); } }; private final ModelObserver<SyncIndicator> syncIndicatorModelObserver = new ModelObserver<SyncIndicator>() { @Override public void updated(SyncIndicator syncIndicator) { switch (syncIndicator.getState()) { case SYNCING: case WAITING: conversationLoadingIndicatorViewView.show(); getControllerFactory().getLoadTimeLoggerController().conversationContentSyncStart(); return; case COMPLETED: case FAILED: default: conversationLoadingIndicatorViewView.hide(); getControllerFactory().getLoadTimeLoggerController().conversationContentSyncFinish(); } } }; private final MessageContent.Asset.ErrorHandler assetErrorHandler = new MessageContent.Asset.ErrorHandler() { @Override public void noWifiAndFileIsLarge(long sizeInBytes, NetworkMode net, final MessageContent.Asset.Answer answer) { if (getActivity() == null) { answer.ok(); return; } AlertDialog dialog = ViewUtils.showAlertDialog(getActivity(), R.string.asset_upload_warning__large_file__title, R.string.asset_upload_warning__large_file__message_default, R.string.asset_upload_warning__large_file__button_accept, R.string.asset_upload_warning__large_file__button_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { answer.ok(); } }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { answer.cancel(); } }); dialog.setCancelable(false); if (sizeInBytes > 0) { String fileSize = Formatter.formatFileSize(getContext(), sizeInBytes); dialog.setMessage(getString(R.string.asset_upload_warning__large_file__message, fileSize)); } } }; private final MessageContent.Asset.ErrorHandler assetErrorHandlerVideo = new MessageContent.Asset.ErrorHandler() { @Override public void noWifiAndFileIsLarge(long sizeInBytes, NetworkMode net, final MessageContent.Asset.Answer answer) { if (getActivity() == null) { answer.ok(); return; } AlertDialog dialog = ViewUtils.showAlertDialog(getActivity(), R.string.asset_upload_warning__large_file__title, R.string.asset_upload_warning__large_file__message_default, R.string.asset_upload_warning__large_file__button_accept, R.string.asset_upload_warning__large_file__button_cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { answer.ok(); } }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { answer.cancel(); } }); dialog.setCancelable(false); if (sizeInBytes > 0) { dialog.setMessage(getString(R.string.asset_upload_warning__large_file__message__video)); } } }; private final MessageBottomSheetDialog.Callback messageBottomSheetDialogCallback = new MessageBottomSheetDialog.Callback() { @Override public void onAction(MessageBottomSheetDialog.MessageAction action, Message message) { switch (action) { case COPY: copyMessage(message); getControllerFactory().getTrackingController() .tagEvent(OpenedMessageActionEvent.copy(message.getMessageType().name())); break; case DELETE_GLOBAL: deleteMessage(message, true); getControllerFactory().getTrackingController() .tagEvent(OpenedMessageActionEvent.deleteForEveryone(message.getMessageType().name())); break; case DELETE_LOCAL: deleteMessage(message, false); getControllerFactory().getTrackingController() .tagEvent(OpenedMessageActionEvent.deleteForMe(message.getMessageType().name())); break; case EDIT: editMessage(message); getControllerFactory().getTrackingController() .tagEvent(OpenedMessageActionEvent.edit(message.getMessageType().name())); break; case FORWARD: forwardMessage(message); getControllerFactory().getTrackingController() .tagEvent(OpenedMessageActionEvent.forward(message.getMessageType().name())); break; case LIKE: case UNLIKE: if (message.isLikedByThisUser()) { message.unlike(); getControllerFactory().getTrackingController().tagEvent(ReactedToMessageEvent .unlike(message.getConversation(), message, ReactedToMessageEvent.Method.MENU)); } else { message.like(); getControllerFactory().getUserPreferencesController() .setPerformedAction(IUserPreferencesController.LIKED_MESSAGE); getControllerFactory().getTrackingController().tagEvent(ReactedToMessageEvent .like(message.getConversation(), message, ReactedToMessageEvent.Method.MENU)); } break; case SAVE: saveMessage(message); break; default: ExceptionHandler.saveException(new RuntimeException("Unhandled action"), null, null); } } }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Lifecycle // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { if (nextAnim == 0 || getContainer() == null || getControllerFactory().isTornDown()) { return super.onCreateAnimation(transit, enter, nextAnim); } else if (nextAnim == R.anim.fragment_animation_swap_profile_conversation_tablet_in || nextAnim == R.anim.fragment_animation_swap_profile_conversation_tablet_out) { int width = ViewUtils.getOrientationDependentDisplayWidth(getActivity()) - getResources().getDimensionPixelSize(R.dimen.framework__sidebar_width); return new MessageStreamAnimation(enter, getResources().getInteger(R.integer.wire__animation__duration__medium), 0, width); } else if (getControllerFactory().getPickUserController().isHideWithoutAnimations()) { return new ConversationListAnimation(0, getResources().getDimensionPixelSize( R.dimen.open_new_conversation__thread_list__max_top_distance), enter, 0, 0, false, 1f); } else if (enter) { return new ConversationListAnimation(0, getResources() .getDimensionPixelSize(R.dimen.open_new_conversation__thread_list__max_top_distance), enter, getResources().getInteger(R.integer.framework_animation_duration_long), getResources().getInteger(R.integer.framework_animation_duration_medium), false, 1f); } return new ConversationListAnimation(0, getResources().getDimensionPixelSize(R.dimen.open_new_conversation__thread_list__max_top_distance), enter, getResources().getInteger(R.integer.framework_animation_duration_medium), 0, false, 1f); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); assetIntentsManager = new AssetIntentsManager(getActivity(), this, savedInstanceState); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (messageStreamManager != null) { messageStreamManager.onConfigurationChanged(getContext(), messageAdapter); } if (BuildConfig.SHOW_MENTIONING) { getControllerFactory().getMentioningController().hide(); } audioMessageRecordingView.requestLayout(); audioMessageRecordingView.invalidate(); if (LayoutSpec.isTablet(getContext()) && listView != null && messageAdapter != null) { // To clear ListView cache as we use different views for portrait and landscape listView.setAdapter(messageAdapter); } if (ViewUtils.isInLandscape(newConfig)) { toolbar.setNavigationIcon(null); } else { if (ThemeUtils.isDarkTheme(getContext())) { toolbar.setNavigationIcon(R.drawable.ic_action_menu_light); } else { toolbar.setNavigationIcon(R.drawable.ic_action_menu_dark); } } toolbar.setContentInsetsRelative(getResources().getDimensionPixelSize(R.dimen.content__padding_left), toolbar.getContentInsetEnd()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_conversation, viewGroup, false); extendedCursorContainer = ViewUtils.getView(view, R.id.ecc__conversation); containerPreview = ViewUtils.getView(view, R.id.fl__conversation_overlay); cursorLayout = ViewUtils.getView(view, R.id.cl__cursor); audioMessageRecordingView = ViewUtils.getView(view, R.id.amrv_audio_message_recording); toolbar = ViewUtils.getView(view, R.id.t_conversation_toolbar); toolbarTitle = ViewUtils.getView(toolbar, R.id.tv__conversation_toolbar__title); typingIndicatorView = ViewUtils.getView(view, R.id.tiv_typing_indicator_view); typingIndicatorView.setCallback(this); toolbar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getControllerFactory().getConversationScreenController().showParticipants(toolbar, false); } }); toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.action_audio_call: getControllerFactory().getCallingController().startCall(false); cursorLayout.closeEditMessage(false); return true; case R.id.action_video_call: getControllerFactory().getCallingController().startCall(true); cursorLayout.closeEditMessage(false); return true; } return false; } }); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (LayoutSpec.isTablet(getContext()) && ViewUtils.isInLandscape(getContext())) { return; } cursorLayout.closeEditMessage(false); getActivity().onBackPressed(); KeyboardUtils.closeKeyboardIfShown(getActivity()); } }); if (LayoutSpec.isTablet(getContext()) && ViewUtils.isInLandscape(getContext())) { toolbar.setNavigationIcon(null); } conversationLoadingIndicatorViewView = ViewUtils.getView(view, R.id.lbv__conversation__loading_indicator); if (BuildConfig.SHOW_MENTIONING) { getChildFragmentManager().beginTransaction() .add(R.id.fl__conversation_overlay, MentioningFragment.getInstance(), MentioningFragment.TAG) .commit(); } listView = ViewUtils.getView(view, R.id.clv__conversation_list_view); listView.setVerticalScrollBarEnabled(false); listView.setRecyclerListener(new AbsListView.RecyclerListener() { @Override public void onMovedToScrapHeap(View view) { if (view == null || view.getTag() == null || !(view.getTag() instanceof MessageViewController)) { return; } MessageViewController viewTag = (MessageViewController) view.getTag(); // We want to ignore those because it looks weird if the images and so on fades in again if (viewTag instanceof ImageMessageViewController || viewTag instanceof YouTubeMessageViewController || viewTag instanceof MediaPlayerViewController) { return; } viewTag.recycle(); } }); messageAdapter = new MessageAdapter(this); listView.setAdapter(messageAdapter); messageStreamManager = new MessageStreamManager(listView, messageAdapter); // invisible footer to scroll over inputfield invisibleFooter = new FrameLayout(getActivity()); AbsListView.LayoutParams params = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getResources().getDimensionPixelSize(R.dimen.cursor__list_view_footer__height)); invisibleFooter.setLayoutParams(params); listView.addFooterView(invisibleFooter, null, false); cursorLayout.showSendButton(false); // Recording audio messages audioMessageRecordingView.setCallback(this); if (LayoutSpec.isTablet(getActivity())) { view.setBackgroundColor(Color.WHITE); } if (savedInstanceState != null) { isPreviewShown = savedInstanceState.getBoolean(SAVED_STATE_PREVIEW, false); } return view; } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); audioMessageRecordingView.setVisibility(View.INVISIBLE); } @Override public void onStart() { super.onStart(); getControllerFactory().getGlobalLayoutController().addKeyboardHeightObserver(extendedCursorContainer); getControllerFactory().getGlobalLayoutController().addKeyboardVisibilityObserver(extendedCursorContainer); getControllerFactory().getRequestPermissionsController().addObserver(this); cursorLayout.setCursorCallback(this); cursorLayout.showSendButtonAsEnterKey( !getControllerFactory().getUserPreferencesController().isCursorSendButtonEnabled()); hideSendButtonIfNeeded(); final String draftText = getStoreFactory().getDraftStore() .getDraft(getStoreFactory().getConversationStore().getCurrentConversation()); if (!TextUtils.isEmpty(draftText)) { cursorLayout.setText(draftText); } if (BuildConfig.SHOW_MENTIONING) { getControllerFactory().getMentioningController().addObserver(this); } messagesListModelObserver.resumeListening(); syncIndicatorModelObserver.resumeListening(); audioMessageRecordingView.setDarkTheme(getControllerFactory().getThemeController().isDarkTheme()); getControllerFactory().getConversationScreenController() .setConversationStreamUiReady(messageStreamManager.getCount() > 0); if (!getControllerFactory().getConversationScreenController().isConversationStreamUiInitialized()) { getStoreFactory().getConversationStore().addConversationStoreObserverAndUpdate(this); } else { getStoreFactory().getConversationStore().addConversationStoreObserver(this); } getControllerFactory().getNavigationController().addNavigationControllerObserver(this); getControllerFactory().getNavigationController().addPagerControllerObserver(this); getControllerFactory().getGiphyController().addObserver(this); getControllerFactory().getSingleImageController().addSingleImageObserver(this); getControllerFactory().getStreamMediaPlayerController().addStreamMediaBarObserver(this); getControllerFactory().getAccentColorController().addAccentColorObserver(this); getStoreFactory().getParticipantsStore().addParticipantsStoreObserver(this); listView.registerScrolledToBottomListener(this); listView.registVisibleMessagesChangedListener(this); getControllerFactory().getGlobalLayoutController().addKeyboardVisibilityObserver(this); getStoreFactory().getInAppNotificationStore().addInAppNotificationObserver(this); getControllerFactory().getSlidingPaneController().addObserver(this); extendedCursorContainer.setCallback(this); } @Override public void onResume() { super.onResume(); if (LayoutSpec.isTablet(getContext())) { conversationModelObserver .setAndUpdate(getStoreFactory().getConversationStore().getCurrentConversation()); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); assetIntentsManager.onSaveInstanceState(outState); outState.putBoolean(SAVED_STATE_PREVIEW, isPreviewShown); } @Override public void onPause() { super.onPause(); KeyboardUtils.hideKeyboard(getActivity()); hideAudioMessageRecording(); if (messageBottomSheetDialog != null) { if (messageBottomSheetDialog.isShowing()) { messageBottomSheetDialog.dismiss(); } messageBottomSheetDialog = null; } } @Override public void onStop() { extendedCursorContainer.close(true); extendedCursorContainer.setCallback(null); cursorLayout.setCursorCallback(null); getControllerFactory().getGlobalLayoutController().removeKeyboardHeightObserver(extendedCursorContainer); getControllerFactory().getGlobalLayoutController() .removeKeyboardVisibilityObserver(extendedCursorContainer); if (BuildConfig.SHOW_MENTIONING) { getControllerFactory().getMentioningController().removeObserver(this); } getControllerFactory().getGiphyController().removeObserver(this); getControllerFactory().getSingleImageController().removeSingleImageObserver(this); if (!cursorLayout.isEditingMessage()) { getStoreFactory().getDraftStore().setDraft( getStoreFactory().getConversationStore().getCurrentConversation(), cursorLayout.getText().trim()); } getStoreFactory().getInAppNotificationStore().removeInAppNotificationObserver(this); getStoreFactory().getParticipantsStore().removeParticipantsStoreObserver(this); getControllerFactory().getStreamMediaPlayerController().removeStreamMediaBarObserver(this); getControllerFactory().getGlobalLayoutController().removeKeyboardVisibilityObserver(this); getControllerFactory().getNavigationController().removePagerControllerObserver(this); messagesListModelObserver.pauseListening(); syncIndicatorModelObserver.pauseListening(); getStoreFactory().getConversationStore().removeConversationStoreObserver(this); getControllerFactory().getAccentColorController().removeAccentColorObserver(this); getControllerFactory().getNavigationController().removeNavigationControllerObserver(this); listView.unregistVisibleMessagesChangedListener(this); listView.unregisterScrolledToBottomListener(this); getControllerFactory().getSlidingPaneController().removeObserver(this); getControllerFactory().getConversationScreenController().setConversationStreamUiReady(false); getControllerFactory().getRequestPermissionsController().removeObserver(this); super.onStop(); } @Override public void onDestroyView() { containerPreview = null; listView = null; messageAdapter = null; cursorLayout.tearDown(); cursorLayout = null; conversationLoadingIndicatorViewView = null; if (inputStateIndicator != null) { inputStateIndicator.removeUpdateListener(typingListener); inputStateIndicator = null; } typingIndicatorView.clear(); typingIndicatorView = null; typingListener = null; conversationModelObserver.clear(); toolbarTitle = null; toolbar = null; super.onDestroyView(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Container implementations // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public void onShowSingleImage(Message message) { listView.setEnabled(true); } @Override public void onShowUserImage(User user) { } @Override public void onHideSingleImage() { getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); listView.setEnabled(true); } @Override public void updateSingleImageReferences() { listView.postDelayed(new Runnable() { @Override public void run() { if (getControllerFactory() == null || getControllerFactory().isTornDown()) { return; } Message message = getControllerFactory().getSingleImageController().getMessage(); if (message == null) { getControllerFactory().getSingleImageController().setContainerOutOfScreen(true); return; } // Single image is visible int messagePosition = messageStreamManager.getIndexOfMessage(message); View messageView = getViewByPosition(messagePosition, listView); if (messageView == null) { getControllerFactory().getSingleImageController().setContainerOutOfScreen(true); return; } final ImageView clickedImageView = ViewUtils.getView(messageView, R.id.iv__row_conversation__message_image); getControllerFactory().getSingleImageController().setViewReferences(clickedImageView); } }, getResources().getInteger(R.integer.framework_animation_duration_long)); } private View getViewByPosition(int pos, ListView listView) { final int firstListItemPosition = listView.getFirstVisiblePosition(); final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1; if (pos < firstListItemPosition || pos > lastListItemPosition) { return listView.getAdapter().getView(pos, null, listView); } else { final int childIndex = pos - firstListItemPosition; return listView.getChildAt(childIndex); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Notifications // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public void onConversationListUpdated(@NonNull ConversationsList conversationsList) { } @Override public void onConversationListStateHasChanged(ConversationsList.ConversationsListState state) { } @Override public void onCurrentConversationHasChanged(final IConversation fromConversation, final IConversation toConversation, final ConversationChangeRequester conversationChangeRequester) { if (toConversation == null) { return; } if (LayoutSpec.isPhone(getContext())) { conversationModelObserver.setAndUpdate(toConversation); } if (isPreviewShown) { onCancelPreview(); } extendedCursorContainer.close(true); messageStreamManager.setConversation(toConversation, getControllerFactory().getNavigationController().getCurrentPage() != Page.MESSAGE_STREAM); getControllerFactory().getConversationScreenController() .setSingleConversation(toConversation.getType() == IConversation.Type.ONE_TO_ONE); if (BuildConfig.SHOW_MENTIONING) { getControllerFactory().getMentioningController().setCurrentConversation(toConversation); } int duration = getResources().getInteger(R.integer.framework_animation_duration_short); // post to give the RootFragment the chance to drive its animations first new Handler().postDelayed(new Runnable() { @Override public void run() { if (cursorLayout == null) { return; } final boolean changeToDifferentConversation = fromConversation == null || !fromConversation.getId().equals(toConversation.getId()); // handle draft if (fromConversation != null && changeToDifferentConversation && !cursorLayout.isEditingMessage()) { getStoreFactory().getDraftStore().setDraft(fromConversation, cursorLayout.getText().trim()); } if (toConversation.getType() == IConversation.Type.WAIT_FOR_CONNECTION) { return; } KeyboardUtils.hideKeyboard(getActivity()); conversationLoadingIndicatorViewView.hide(); cursorLayout.enableMessageWriting(); if (changeToDifferentConversation) { getControllerFactory().getConversationScreenController().setConversationStreamUiReady(false); toConversationType = toConversation.getType(); messagesListModelObserver.setAndUpdate(toConversation.getMessages()); getControllerFactory().getSharingController().maybeResetSharedText(fromConversation); getControllerFactory().getSharingController().maybeResetSharedUris(fromConversation); cursorLayout.setVisibility(toConversation.isActive() ? View.VISIBLE : View.GONE); if (!inSplitPortraitMode() && listView.computeIsScrolledToBottom()) { resetCursor(); } final String draftText = getStoreFactory().getDraftStore().getDraft(toConversation); if (TextUtils.isEmpty(draftText)) { resetCursor(); } else { cursorLayout.setText(draftText); } cursorLayout.setConversation(toConversation); hideAudioMessageRecording(); } final boolean isSharing = getControllerFactory().getSharingController() .isSharedConversation(toConversation); final boolean isSharingText = !TextUtils .isEmpty(getControllerFactory().getSharingController().getSharedText()) && isSharing; final List<Uri> sharedFileUris = getControllerFactory().getSharingController().getSharedFileUris(); final boolean isSharingFiles = !(sharedFileUris == null || sharedFileUris.isEmpty()) && isSharing; if (isSharing) { if (isSharingText) { final String draftText = getControllerFactory().getSharingController().getSharedText(); if (TextUtils.isEmpty(draftText)) { resetCursor(); } else { cursorLayout.setText(draftText); } cursorLayout.enableMessageWriting(); KeyboardUtils.showKeyboard(getActivity()); getControllerFactory().getSharingController().maybeResetSharedText(toConversation); } else if (isSharingFiles) { if (PermissionUtils.hasSelfPermissions(getActivity(), FILE_SHARING_PERMISSION)) { for (Uri uri : sharedFileUris) { getStoreFactory().getConversationStore() .sendMessage(AssetFactory.fromContentUri(uri), assetErrorHandler); } } else { sharingUris.addAll(sharedFileUris); ActivityCompat.requestPermissions(getActivity(), FILE_SHARING_PERMISSION, FILE_SHARING_PERMISSION_REQUEST_ID); } getControllerFactory().getSharingController().maybeResetSharedUris(toConversation); } } if (!getControllerFactory().getStreamMediaPlayerController() .isSelectedConversation(toConversation.getId())) { onHideMediaBar(); } if (inputStateIndicator != null) { inputStateIndicator.getTypingUsers().removeUpdateListener(typingListener); } inputStateIndicator = toConversation.getInputStateIndicator(); typingIndicatorView.setInputStateIndicator(inputStateIndicator); if (inputStateIndicator != null) { inputStateIndicator.getTypingUsers().addUpdateListener(typingListener); } } }, duration); // Saving factories since this fragment may be re-created before the runnable is done, // but we still want runnable to work. final IStoreFactory storeFactory = getStoreFactory(); final IControllerFactory controllerFactory = getControllerFactory(); // TODO: Remove when call issue is resolved with https://wearezeta.atlassian.net/browse/CM-645 // And also why do we use the ConversationFragment to start a call from somewhere else.... new Handler().postDelayed(new Runnable() { @Override public void run() { if (storeFactory == null || storeFactory.isTornDown() || controllerFactory == null || controllerFactory.isTornDown()) { return; } switch (conversationChangeRequester) { case START_CONVERSATION_FOR_VIDEO_CALL: controllerFactory.getCallingController().startCall(true); break; case START_CONVERSATION_FOR_CALL: controllerFactory.getCallingController().startCall(false); break; case START_CONVERSATION_FOR_CAMERA: controllerFactory.getCameraController().openCamera(CameraContext.MESSAGE); break; } } }, 1000); } @Override public void onConversationSyncingStateHasChanged(SyncState syncState) { } @Override public void onMenuConversationHasChanged(IConversation fromConversation) { } @Override public void onVerificationStateChanged(String conversationId, Verification previousVerification, Verification currentVerification) { } @Override public void onCursorPositionChanged(float x, float y) { } @Override public void onQueryResultChanged(@NonNull List<User> usersList) { } @Override public void onMentionedUserSelected(@NonNull String query, @NonNull User user) { final int cursorPosition = cursorLayout.getSelection(); final String text = cursorLayout.getText(); if (cursorPosition == -1 || TextUtils.isEmpty(text)) { cursorLayout.setText(text); cursorLayout.setSelection(text.length()); return; } final String[] words = text.split(" "); StringBuilder builder = new StringBuilder(); int desiredCursorPosition = 0; boolean inserted = false; for (int i = 0, wordsLength = words.length; i < wordsLength; i++) { String word = words[i]; if (!inserted && builder.length() + word.length() + 1 > cursorPosition) { final int diff = word.length() - 1 - query.length(); String rest = word.substring(query.length() + 1); builder.append('@').append(user.getDisplayName()).append(' '); if (!TextUtils.isEmpty(rest)) { builder.append(rest); } desiredCursorPosition = builder.length() - diff; inserted = true; } else { builder.append(word); } if (i < wordsLength - 1) { builder.append(' '); } } cursorLayout.setText(builder.toString()); cursorLayout.setSelection(desiredCursorPosition); } @Override public void onScrolledToBottom() { getStoreFactory().getInAppNotificationStore().onScrolledToBottom(); messageStreamManager.onScrolledToBottom(true); cursorLayout.showTopbar(false); } @Override public void onScrolledAwayFromBottom() { getStoreFactory().getInAppNotificationStore().onScrolledAwayFromBottom(); messageStreamManager.onScrolledToBottom(false); cursorLayout.showTopbar(typingIndicatorView.getVisibility() == View.GONE); } @Override public void onScrollOffsetFromFirstElement(int offset) { } @Override public void onKeyboardVisibilityChanged(boolean keyboardIsVisible, int keyboardHeight, View currentFocus) { cursorLayout.notifyKeyboardVisibilityChanged(keyboardIsVisible, currentFocus); if (keyboardIsVisible && getControllerFactory().getFocusController() .getCurrentFocus() == IFocusController.CONVERSATION_CURSOR && !cursorLayout.isEditingMessage()) { messageStreamManager.onCursorStateEdit(); getControllerFactory().getNavigationController().setMessageStreamState(VoiceBarAppearance.MINI); } if (!keyboardIsVisible) { getControllerFactory().getNavigationController().setMessageStreamState(VoiceBarAppearance.FULL); } } private void showLoadingIndicator(MessagesList messages) { if (messages != null && messages.size() > 0) { conversationLoadingIndicatorViewView.setType(LoadingIndicatorView.INFINITE_LOADING_BAR); } else { conversationLoadingIndicatorViewView.setType(LoadingIndicatorView.SPINNER); } } ////////////////////////////////////////////////////////////////////////////////////////// // // Cursor callback // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onEditTextHasChanged(int cursorPosition, String text) { if (listView == null) { return; } // this is needed to make sure that text is scrolled to bottom - on some devices // the keyboard height changes while text is being entered if (!cursorLayout.isEditingMessage()) { messageStreamManager.onCursorStateEdit(); } if (inputStateIndicator != null) { if (text.isEmpty()) { inputStateIndicator.textCleared(); } else { inputStateIndicator.textChanged(); } } if (getControllerFactory().getUserPreferencesController().isCursorSendButtonEnabled()) { cursorLayout.showSendButton(!TextUtils.isEmpty(text)); } } public boolean isKeyboardUp() { if (getControllerFactory() == null || getControllerFactory().isTornDown()) { return false; } return getControllerFactory().getGlobalLayoutController().isKeyboardVisible(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { assetIntentsManager.onActivityResult(requestCode, resultCode, data); } ////////////////////////////////////////////////////////////////////////////////////////// // // AccentColorObserver // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onAccentColorHasChanged(Object sender, int color) { cursorLayout.setAccentColor(color); conversationLoadingIndicatorViewView.setColor(color); audioMessageRecordingView.setAccentColor(color); extendedCursorContainer.setAccentColor(color); } ////////////////////////////////////////////////////////////////////////////////////////// // // VisibleMessagesChangesListener // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onVisibleMessagesChanged(List<String> visibleMessageIds) { getControllerFactory().getStreamMediaPlayerController().informVisibleItems(visibleMessageIds); } ////////////////////////////////////////////////////////////////////////////////////////// // // StreamMediaBar // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onShowMediaBar(String conversationId) { IConversation currentConversation = getStoreFactory().getConversationStore().getCurrentConversation(); if (currentConversation == null || !conversationId.equals(currentConversation.getId())) { return; } if (streamMediaBarFragment != null) { streamMediaBarFragment.show(); return; } try { FragmentManager fragmentManager = getChildFragmentManager(); streamMediaBarFragment = StreamMediaPlayerBarFragment.newInstance(); fragmentManager.beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) .add(R.id.fl__conversation__mediabar_container, streamMediaBarFragment, StreamMediaPlayerBarFragment.TAG) .commit(); } catch (IllegalStateException e) { Timber.e(e, "onShowMediaBar failed"); } } @Override public void onHideMediaBar() { if (streamMediaBarFragment == null) { return; } streamMediaBarFragment.hide(); } @Override public void onScrollTo(Message message) { if (listView != null) { final int scrollPosition = messageStreamManager.getIndexOfMessage(message); Timber.i("onScrollTo. Smooth scroll list to position %s", scrollPosition); listView.smoothScrollToPosition(scrollPosition); } } @Override public void onIncomingMessage(Message message) { } @Override public void onIncomingKnock(KnockingEvent knock) { } @Override public void conversationUpdated(IConversation conversation) { if (conversation == null || getStoreFactory() == null || getStoreFactory().isTornDown()) { return; } if (!LayoutSpec.isTablet(getActivity())) { toolbarTitle.setText(conversation.getName()); } if (cursorLayout == null) { return; } final IConversation currentConversation = getStoreFactory().getConversationStore().getCurrentConversation(); if (currentConversation == null || !currentConversation.isMemberOfConversation()) { cursorLayout.setVisibility(View.GONE); ViewUtils.setMarginBottom(listView, getResources().getDimensionPixelSize(R.dimen.cursor__list_view_footer_no_cursor__height)); } else { cursorLayout.setVisibility(View.VISIBLE); ViewUtils.setMarginBottom(listView, 0); } } @Override public void participantsUpdated(UsersList participants) { } @Override public void otherUserUpdated(User otherUser) { } ////////////////////////////////////////////////////////////////////////////////////////// // // NavigationController Callback // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onPageVisible(Page page) { if (page == Page.MESSAGE_STREAM) { messagesListModelObserver.forceUpdate(); cursorLayout.enableMessageWriting(); } if (LayoutSpec.isPhone(getContext())) { if (page == Page.MESSAGE_STREAM) { messageStreamManager.resume(); } else { messageStreamManager.pause(); } } } @Override public void onPageStateHasChanged(Page page) { } ////////////////////////////////////////////////////////////////////////////// // // GroupCallingStoreObserver // ////////////////////////////////////////////////////////////////////////////// @Override public void onStartCall(boolean withVideo) { } ////////////////////////////////////////////////////////////////////////////// // // MessageViewsContainer // ////////////////////////////////////////////////////////////////////////////// @Override public int getUnreadMessageCount() { return messageStreamManager.getUnreadMessageCount(); } @Override public IConversation.Type getConversationType() { return toConversationType; } @Override public void setExpandedMessageId(String messageId) { expandedMessageId = messageId; } @Override public String getExpandedMessageId() { return expandedMessageId; } @Override public void setExpandedView(ExpandableView expandedView) { currentExpandableView = expandedView; } @Override public ExpandableView getExpandedView() { return currentExpandableView; } @Override public boolean ping(boolean hotKnock, String id, String message, int color) { if (hotKnock) { if (lastHotPingMessageId != null && lastHotPingMessageId.equals(id)) { return false; } lastHotPingMessageId = id; } else { if (lastPingMessageId != null && lastPingMessageId.equals(id)) { return false; } lastPingMessageId = id; } return true; } @Override public boolean isPhone() { return LayoutSpec.isPhone(getActivity()); } @Override public void openSpotifySettings() { startActivity(ZetaPreferencesActivity.getSpotifyLoginIntent(getActivity())); } @Override public void openDevicesPage(OtrDestination otrDestination, View anchorView) { if (isKeyboardUp()) { KeyboardUtils.hideKeyboard(getActivity()); } switch (otrDestination) { case PREFERENCES: startActivity(ZetaPreferencesActivity.getOtrDevicesPreferencesIntent(getContext())); break; case PARTICIPANTS: getControllerFactory().getConversationScreenController().showParticipants(anchorView, true); break; } } @Override public boolean onItemLongClick(final Message message) { final boolean isMemberOfConversation = getStoreFactory().getConversationStore().getCurrentConversation() .isMemberOfConversation(); if (messageBottomSheetDialog != null) { if (messageBottomSheetDialog.isShowing()) { messageBottomSheetDialog.dismiss(); } messageBottomSheetDialog = null; } if (KeyboardUtils.isKeyboardVisible(getContext())) { KeyboardUtils.hideKeyboard(getActivity()); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { if (getActivity() == null) { return; } messageBottomSheetDialog = new MessageBottomSheetDialog(getContext(), message, isMemberOfConversation, messageBottomSheetDialogCallback); messageBottomSheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { messageBottomSheetDialog = null; } }); messageBottomSheetDialog.show(); } }, BOTTOM_MENU_DISPLAY_DELAY_MS); } else { messageBottomSheetDialog = new MessageBottomSheetDialog(getContext(), message, isMemberOfConversation, messageBottomSheetDialogCallback); messageBottomSheetDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { messageBottomSheetDialog = null; } }); messageBottomSheetDialog.show(); } return true; } @Override public void onOpenUrl(String url) { getContainer().onOpenUrl(url); } @Override public void openSettings() { startActivity(ZetaPreferencesActivity.getDefaultIntent(getActivity())); } @Override public boolean isTornDown() { return getContainer() == null || getControllerFactory().isTornDown() || getStoreFactory().isTornDown(); } ////////////////////////////////////////////////////////////////////////////// // // CurrentFocusObserver // ////////////////////////////////////////////////////////////////////////////// private boolean inSplitPortraitMode() { return LayoutSpec.isTablet(getActivity()) && ViewUtils.isInPortrait(getActivity()) && getControllerFactory().getNavigationController().getPagerPosition() == 0; } @Override public void onSearch(String keyword) { } @Override public void onRandomSearch() { } @Override public void onTrendingSearch() { } @Override public void onCloseGiphy() { resetCursor(); } @Override public void onCancelGiphy() { } ////////////////////////////////////////////////////////////////////////////// // // SlidingPaneObserver // ////////////////////////////////////////////////////////////////////////////// @Override public void onPanelSlide(View panel, float slideOffset) { } @Override public void onPanelOpened(View panel) { if (ViewUtils.isInLandscape(getActivity())) { return; } KeyboardUtils.closeKeyboardIfShown(getActivity()); } @Override public void onPanelClosed(View panel) { } private void resetCursor() { cursorLayout.setText(""); } @Override public void dismissOnBoardingHint(OnBoardingHintType requestedType) { } @Override public void onSyncError(final ErrorsList.ErrorDescription errorDescription) { switch (errorDescription.getType()) { case CANNOT_SEND_ASSET_FILE_NOT_FOUND: ViewUtils.showAlertDialog(getActivity(), R.string.asset_upload_error__not_found__title, R.string.asset_upload_error__not_found__message, R.string.asset_upload_error__not_found__button, null, true); errorDescription.dismiss(); break; case CANNOT_SEND_ASSET_TOO_LARGE: AlertDialog dialog = ViewUtils.showAlertDialog(getActivity(), R.string.asset_upload_error__file_too_large__title, R.string.asset_upload_error__file_too_large__message_default, R.string.asset_upload_error__file_too_large__button, null, true); long maxAllowedSizeInBytes = AssetFactory.getMaxAllowedAssetSizeInBytes(); if (maxAllowedSizeInBytes > 0) { String maxFileSize = Formatter.formatShortFileSize(getContext(), maxAllowedSizeInBytes); dialog.setMessage(getString(R.string.asset_upload_error__file_too_large__message, maxFileSize)); } errorDescription.dismiss(); getControllerFactory().getTrackingController().tagEvent(new SelectedTooLargeFileEvent()); break; case RECORDING_FAILURE: ViewUtils.showAlertDialog(getActivity(), R.string.audio_message__recording__failure__title, R.string.audio_message__recording__failure__message, R.string.alert_dialog__confirmation, null, true); errorDescription.dismiss(); break; case CANNOT_SEND_MESSAGE_TO_UNVERIFIED_CONVERSATION: onErrorCanNotSentMessageToUnverifiedConversation(errorDescription); break; } } @SuppressLint("NewApi") @Override public void onCursorButtonClicked(CursorMenuItem cursorMenuItem) { final boolean isGroupConversation = getConversationType() == IConversation.Type.GROUP; switch (cursorMenuItem) { case AUDIO_MESSAGE: if (PermissionUtils.hasSelfPermissions(getActivity(), AUDIO_PERMISSION)) { openExtendedCursor(ExtendedCursorContainer.Type.VOICE_FILTER_RECORDING); } else { ActivityCompat.requestPermissions(getActivity(), AUDIO_PERMISSION, AUDIO_FILTER_PERMISSION_REQUEST_ID); } break; case CAMERA: if (LayoutSpec.isTablet(getContext())) { KeyboardUtils.closeKeyboardIfShown(getActivity()); getControllerFactory().getCameraController().openCamera(CameraContext.MESSAGE); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.photo(isGroupConversation)); } else { if (PermissionUtils.hasSelfPermissions(getContext(), EXTENDED_CURSOR_PERMISSIONS)) { openExtendedCursor(ExtendedCursorContainer.Type.IMAGES); } else { ActivityCompat.requestPermissions(getActivity(), EXTENDED_CURSOR_PERMISSIONS, OPEN_EXTENDED_CURSOR_IMAGES); } } break; case PING: getStoreFactory().getNetworkStore().doIfHasInternetOrNotifyUser(new DefaultNetworkAction() { @Override public void execute(NetworkMode networkMode) { getStoreFactory().getConversationStore().knockCurrentConversation(); getStoreFactory().getMediaStore().playSound(R.raw.ping_from_me); getControllerFactory().getTrackingController() .updateSessionAggregates(RangedAttribute.PINGS_SENT); } }); TrackingUtils.onSentPingMessage(getControllerFactory().getTrackingController(), getStoreFactory().getConversationStore().getCurrentConversation()); break; case SKETCH: getControllerFactory().getDrawingController().showDrawing(null, IDrawingController.DrawingDestination.SKETCH_BUTTON); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.sketch(isGroupConversation)); break; case FILE: assetIntentsManager.openFileSharing(); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.file(isGroupConversation)); break; case VIDEO_MESSAGE: getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.videomessage(isGroupConversation)); isVideoMessageButtonClicked = true; getCameraController().releaseCamera(new Callback<Void>() { @Override public void callback(Void v) { if (!isVideoMessageButtonClicked || assetIntentsManager == null) { return; } isVideoMessageButtonClicked = false; assetIntentsManager.maybeCaptureVideo(getActivity(), AssetIntentsManager.IntentType.VIDEO_CURSOR_BUTTON); } }); break; case LOCATION: if (ConnectionResult.SUCCESS == GooglePlayServicesUtil.isGooglePlayServicesAvailable(getContext())) { KeyboardUtils.hideKeyboard(getActivity()); getControllerFactory().getLocationController().showShareLocation(); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.location(isGroupConversation)); } else { Toast.makeText(getContext(), R.string.location_sharing__missing_play_services, Toast.LENGTH_LONG) .show(); } break; case MORE: case LESS: getControllerFactory().getTrackingController() .tagEvent(new OpenedMoreActionsEvent(getConversationTypeString())); break; case GIF: getControllerFactory().getGiphyController().handleInput(cursorLayout.getText()); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.giphy(isGroupConversation)); break; } } private GlobalCameraController getCameraController() { return ((BaseScalaActivity) getActivity()).injectJava(GlobalCameraController.class); } private void openExtendedCursor(ExtendedCursorContainer.Type type) { final boolean isGroupConversation = getConversationType() == IConversation.Type.GROUP; switch (type) { case NONE: break; case VOICE_FILTER_RECORDING: extendedCursorContainer.openVoiceFilter(this); hideSendButtonIfNeeded(); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.audiomessage(isGroupConversation)); break; case IMAGES: extendedCursorContainer.openCursorImages(this); hideSendButtonIfNeeded(); getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.photo(isGroupConversation)); break; } } @Override public void onCursorButtonLongPressed(CursorMenuItem cursorMenuItem) { switch (cursorMenuItem) { case AUDIO_MESSAGE: if (PermissionUtils.hasSelfPermissions(getActivity(), AUDIO_PERMISSION)) { extendedCursorContainer.close(true); if (audioMessageRecordingView.getVisibility() == View.VISIBLE) { break; } getControllerFactory().getVibratorController().vibrate(R.array.alert); audioMessageRecordingView.prepareForRecording(); audioMessageRecordingView.setVisibility(View.VISIBLE); final IConversation conversation = getStoreFactory().getConversationStore() .getCurrentConversation(); final boolean isGroupConversation = conversation.getType() == IConversation.Type.GROUP; getControllerFactory().getTrackingController() .tagEvent(OpenedMediaActionEvent.audiomessage(isGroupConversation)); getControllerFactory().getTrackingController() .tagEvent(new StartedRecordingAudioMessageEvent(getConversationTypeString(), true)); } else { ActivityCompat.requestPermissions(getActivity(), AUDIO_PERMISSION, AUDIO_PERMISSION_REQUEST_ID); } break; } } @Override public void onMotionEventFromCursorButton(CursorMenuItem cursorMenuItem, MotionEvent motionEvent) { if (cursorMenuItem != CursorMenuItem.AUDIO_MESSAGE || audioMessageRecordingView == null || audioMessageRecordingView.getVisibility() == View.INVISIBLE) { return; } audioMessageRecordingView.onMotionEventFromAudioMessageButton(motionEvent); } @Override public void onMessageSubmitted(String message) { if (TextUtils.isEmpty(message.trim())) { return; } resetCursor(); getStoreFactory().getConversationStore().sendMessage(message); TrackingUtils.onSentTextMessage(getControllerFactory().getTrackingController(), getStoreFactory().getConversationStore().getCurrentConversation()); getStoreFactory().getNetworkStore().doIfHasInternetOrNotifyUser(null); getControllerFactory().getTrackingController().updateSessionAggregates(RangedAttribute.TEXT_MESSAGES_SENT, message); getControllerFactory().getSharingController() .maybeResetSharedText(getStoreFactory().getConversationStore().getCurrentConversation()); } public void onFocusChange(boolean hasFocus) { if (cursorLayout == null) { return; } if (hasFocus) { getControllerFactory().getFocusController().setFocus(IFocusController.CONVERSATION_CURSOR); } if (LayoutSpec.isPhone(getActivity()) || !getControllerFactory().getPickUserController() .isShowingPickUser(IPickUserController.Destination.CONVERSATION_LIST)) { return; } // On tablet, apply Page.MESSAGE_STREAM soft input mode when conversation cursor has focus (soft input mode of page gets changed when left startui is open) int softInputMode = hasFocus ? getControllerFactory().getGlobalLayoutController().getSoftInputModeForPage(Page.MESSAGE_STREAM) : getControllerFactory().getGlobalLayoutController().getSoftInputModeForPage(Page.PICK_USER); ViewUtils.setSoftInputMode(getActivity().getWindow(), softInputMode, TAG); } @Override public void onCursorClicked() { if (!cursorLayout.isEditingMessage()) { listView.scrollToBottom(); } } @Override public void onShowedActionHint(CursorMenuItem item) { getControllerFactory().getTrackingController() .tagEvent(new OpenedActionHintEvent(item.name(), getConversationTypeString())); } @Override public void onApprovedMessageEditing(Message message) { KeyboardUtils.hideKeyboard(getActivity()); getControllerFactory().getTrackingController().tagEvent(new EditedMessageEvent(message)); } @Override public void onClosedMessageEditing() { getControllerFactory().getConversationScreenController().setMessageBeingEdited(null); messageAdapter.notifyDataSetChanged(); } @Override public void onEmojiButtonClicked(boolean showEmojiKeyboard) { if (showEmojiKeyboard) { KeyboardUtils.hideKeyboard(getActivity()); extendedCursorContainer.openEmojis( getControllerFactory().getUserPreferencesController().getRecentEmojis(), getControllerFactory().getUserPreferencesController().getUnsupportedEmojis(), this); boolean withBot = getStoreFactory().getConversationStore().getCurrentConversation().isOtto(); getControllerFactory().getTrackingController().tagEvent(new OpenedEmojiKeyboardEvent(withBot)); cursorLayout.showSendButton(true); } else { extendedCursorContainer.close(false); KeyboardUtils.showKeyboard(getActivity()); } } @Override public boolean onBackPressed() { if (isPreviewShown) { onCancelPreview(); return true; } if (getChildFragmentManager().getBackStackEntryCount() > 0) { getChildFragmentManager().popBackStack(); return true; } if (extendedCursorContainer.isExpanded()) { extendedCursorContainer.close(false); return true; } if (cursorLayout.isEditingMessage()) { cursorLayout.closeEditMessage(true); return true; } return false; } @Override public void onRequestPermissionsResult(int requestCode, int[] grantResults) { if (assetIntentsManager.onRequestPermissionsResult(requestCode, grantResults)) { return; } switch (requestCode) { case OPEN_EXTENDED_CURSOR_IMAGES: if (PermissionUtils.verifyPermissions(grantResults)) { openExtendedCursor(ExtendedCursorContainer.Type.IMAGES); } break; case CAMERA_PERMISSION_REQUEST_ID: if (PermissionUtils.verifyPermissions(grantResults)) { Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) { takeVideoIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0); } startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE); } else { onCameraPermissionsFailed(); } break; case FILE_SHARING_PERMISSION_REQUEST_ID: if (PermissionUtils.verifyPermissions(grantResults)) { for (Uri uri : sharingUris) { getStoreFactory().getConversationStore().sendMessage(AssetFactory.fromContentUri(uri), assetErrorHandler); } sharingUris.clear(); } else { ViewUtils.showAlertDialog(getActivity(), R.string.asset_upload_error__not_found__title, R.string.asset_upload_error__not_found__message, R.string.asset_upload_error__not_found__button, null, true); } break; case AUDIO_PERMISSION_REQUEST_ID: // No actions required if permission is granted // TODO: https://wearezeta.atlassian.net/browse/AN-4027 Show information dialog if permission is not granted break; case AUDIO_FILTER_PERMISSION_REQUEST_ID: if (PermissionUtils.verifyPermissions(grantResults)) { openExtendedCursor(ExtendedCursorContainer.Type.VOICE_FILTER_RECORDING); } else { Toast.makeText(getActivity(), R.string.audio_message_error__missing_audio_permissions, Toast.LENGTH_SHORT).show(); } break; case SAVE_IMAGE_PERMISSION_REQUEST_ID: if (PermissionUtils.verifyPermissions(grantResults)) { saveImageAssetToGallery(); } else { unableToSaveImageNoPermissions(); } break; default: break; } } private void onCameraPermissionsFailed() { Toast.makeText(getActivity(), R.string.video_message_error__missing_camera_permissions, Toast.LENGTH_SHORT) .show(); } @Override public void onSendAudioMessage(AudioAssetForUpload audioAssetForUpload, AudioEffect appliedAudioEffect, boolean sentWithQuickAction) { getStoreFactory().getConversationStore().sendMessage(audioAssetForUpload, assetErrorHandler); hideAudioMessageRecording(); TrackingUtils.tagSentAudioMessageEvent(getControllerFactory().getTrackingController(), audioAssetForUpload, appliedAudioEffect, true, sentWithQuickAction, getStoreFactory().getConversationStore().getCurrentConversation()); } @Override public void onCancel() { extendedCursorContainer.close(false); } @Override public void onAudioMessageRecordingStarted() { getControllerFactory().getTrackingController() .tagEvent(new StartedRecordingAudioMessageEvent(getConversationTypeString(), false)); } @Override public void sendRecording(AudioAssetForUpload audioAssetForUpload, AudioEffect appliedAudioEffect) { getStoreFactory().getConversationStore().sendMessage(audioAssetForUpload, assetErrorHandler); hideAudioMessageRecording(); TrackingUtils.tagSentAudioMessageEvent(getControllerFactory().getTrackingController(), audioAssetForUpload, appliedAudioEffect, false, false, getStoreFactory().getConversationStore().getCurrentConversation()); extendedCursorContainer.close(true); } @Override public void onCancelledAudioMessageRecording() { hideAudioMessageRecording(); getControllerFactory().getTrackingController() .tagEvent(new CancelledRecordingAudioMessageEvent(getConversationTypeString())); } @Override public void onPreviewedAudioMessage() { getControllerFactory().getTrackingController() .tagEvent(new PreviewedAudioMessageEvent(getConversationTypeString())); } private void hideAudioMessageRecording() { if (audioMessageRecordingView.getVisibility() == View.INVISIBLE) { return; } audioMessageRecordingView.reset(); audioMessageRecordingView.setVisibility(View.INVISIBLE); } private void onErrorCanNotSentMessageToUnverifiedConversation( final ErrorsList.ErrorDescription errorDescription) { if (getControllerFactory().getNavigationController().getCurrentPage() != Page.MESSAGE_STREAM) { return; } errorDescription.dismiss(); KeyboardUtils.hideKeyboard(getActivity()); final IConversation currentConversation = errorDescription.getConversation(); final Iterable<? extends User> users = currentConversation.getUsers(); final Map<User, String> userNameMap = new HashMap<>(); int tmpUnverifiedDevices = 0; int userCount = 0; for (User user : users) { userCount++; if (user.getVerified() == Verification.VERIFIED) { continue; } userNameMap.put(user, user.getDisplayName()); for (OtrClient client : user.getOtrClients()) { if (client.getVerified() == Verification.VERIFIED) { continue; } tmpUnverifiedDevices++; } } final List<String> userNameList = new ArrayList<>(userNameMap.values()); final int userNameCount = userNameList.size(); final String header; if (userNameCount == 0) { header = getResources().getString(R.string.conversation__degraded_confirmation__header__someone); } else if (userNameCount == 1) { final int unverifiedDevices = Math.max(1, tmpUnverifiedDevices); header = getResources().getQuantityString( R.plurals.conversation__degraded_confirmation__header__single_user, unverifiedDevices, userNameList.get(0)); } else { header = getString(R.string.conversation__degraded_confirmation__header__multiple_user, TextUtils.join(", ", userNameList.subList(0, userNameCount - 1)), userNameList.get(userNameCount - 1)); } int tmpMessageCount = 0; for (Message m : errorDescription.getMessages()) { tmpMessageCount++; } final int messageCount = Math.max(1, tmpMessageCount); final String message = getResources() .getQuantityString(R.plurals.conversation__degraded_confirmation__message, messageCount); final ConfirmationCallback callback = new ConfirmationCallback() { @Override public void positiveButtonClicked(boolean checkboxIsSelected) { final Iterable<? extends Message> messages = errorDescription.getMessages(); for (Message message : messages) { message.retry(); } } @Override public void negativeButtonClicked() { } @Override public void canceled() { } @Override public void onHideAnimationEnd(boolean confirmed, boolean canceled, boolean checkboxIsSelected) { if (confirmed || canceled) { return; } final View anchorView = ViewUtils.getView(getActivity(), R.id.cursor_menu_item_participant); getControllerFactory().getConversationScreenController().showParticipants(anchorView, true); } }; final String positiveButton = getString(R.string.conversation__degraded_confirmation__positive_action); final String negativeButton = getResources() .getQuantityString(R.plurals.conversation__degraded_confirmation__negative_action, userCount); final ConfirmationRequest request = new ConfirmationRequest.Builder( IConfirmationController.SEND_MESSAGES_TO_DEGRADED_CONVERSATION).withHeader(header) .withMessage(message).withPositiveButton(positiveButton).withNegativeButton(negativeButton) .withConfirmationCallback(callback).withCancelButton() .withHeaderIcon(R.drawable.shield_half) .withWireTheme(getControllerFactory().getThemeController().getThemeDependentOptionsTheme()) .build(); getControllerFactory().getConfirmationController().requestConfirmation(request, IConfirmationController.CONVERSATION); } private String getConversationTypeString() { return getConversationType() != null ? getConversationType().name() : ""; } @Override public void openCamera() { getControllerFactory().getCameraController().openCamera(CameraContext.MESSAGE); } @Override public void openVideo() { assetIntentsManager.maybeCaptureVideo(getActivity(), AssetIntentsManager.IntentType.VIDEO); } @Override public void openGallery() { assetIntentsManager.openGallery(); } @Override public void onPictureTaken(ImageAsset imageAsset) { showImagePreview(imageAsset, ImagePreviewLayout.Source.CAMERA); } @Override public void onGalleryPictureSelected(ImageAsset asset) { isPreviewShown = true; showImagePreview(asset, ImagePreviewLayout.Source.IN_APP_GALLERY); } private void showImagePreview(ImageAsset asset, ImagePreviewLayout.Source source) { ImagePreviewLayout imagePreviewLayout = createPreviewLayout(); imagePreviewLayout.setImageAsset(asset, source, this); imagePreviewLayout .setAccentColor(getControllerFactory().getAccentColorController().getAccentColor().getColor()); imagePreviewLayout.setTitle(getStoreFactory().getConversationStore().getCurrentConversation().getName()); containerPreview.addView(imagePreviewLayout); openPreview(containerPreview); } private ImagePreviewLayout createPreviewLayout() { return (ImagePreviewLayout) LayoutInflater.from(getContext()) .inflate(R.layout.fragment_cursor_images_preview, containerPreview, false); } private void openPreview(View containerPreview) { isPreviewShown = true; getControllerFactory().getNavigationController().setPagerEnabled(false); containerPreview.setTranslationY(getView().getMeasuredHeight()); containerPreview.animate().translationY(0) .setDuration(getResources().getInteger(R.integer.animation_duration_medium)) .setInterpolator(new Expo.EaseOut()); } @Override public void onCancelPreview() { isPreviewShown = false; getControllerFactory().getNavigationController().setPagerEnabled(true); containerPreview.animate().translationY(getView().getMeasuredHeight()) .setDuration(getResources().getInteger(R.integer.animation_duration_medium)) .setInterpolator(new Expo.EaseIn()).withEndAction(new Runnable() { @Override public void run() { if (containerPreview != null) { containerPreview.removeAllViews(); } } }); } @Override public void onSketchPictureFromPreview(ImageAsset imageAsset, ImagePreviewLayout.Source source) { getControllerFactory().getDrawingController().showDrawing(imageAsset, IDrawingController.DrawingDestination.CAMERA_PREVIEW_VIEW); extendedCursorContainer.close(true); } @Override public void onSendPictureFromPreview(ImageAsset imageAsset, ImagePreviewLayout.Source source) { getStoreFactory().getConversationStore().sendMessage(imageAsset); TrackingUtils.onSentPhotoMessage(getControllerFactory().getTrackingController(), getStoreFactory().getConversationStore().getCurrentConversation(), source); extendedCursorContainer.close(true); onCancelPreview(); } @Override public void onDataReceived(AssetIntentsManager.IntentType type, Uri uri) { switch (type) { case FILE_SHARING: sharingUris.clear(); if (PermissionUtils.hasSelfPermissions(getActivity(), FILE_SHARING_PERMISSION)) { getStoreFactory().getConversationStore().sendMessage(AssetFactory.fromContentUri(uri), assetErrorHandler); } else { sharingUris.add(uri); ActivityCompat.requestPermissions(getActivity(), FILE_SHARING_PERMISSION, FILE_SHARING_PERMISSION_REQUEST_ID); } break; case GALLERY: showImagePreview(ImageAssetFactory.getImageAsset(uri), ImagePreviewLayout.Source.DEVICE_GALLERY); break; case VIDEO_CURSOR_BUTTON: sendVideo(uri); getControllerFactory().getTrackingController() .tagEvent(new SentVideoMessageEvent( (int) (AssetUtils.getVideoAssetDurationMilliSec(getContext(), uri) / 1000), getStoreFactory().getConversationStore().getCurrentConversation(), SentVideoMessageEvent.Source.CURSOR_BUTTON)); break; case VIDEO: sendVideo(uri); getControllerFactory().getTrackingController() .tagEvent(new SentVideoMessageEvent( (int) (AssetUtils.getVideoAssetDurationMilliSec(getContext(), uri) / 1000), getStoreFactory().getConversationStore().getCurrentConversation(), SentVideoMessageEvent.Source.KEYBOARD)); break; case CAMERA: sendImage(uri); TrackingUtils.onSentPhotoMessage(getControllerFactory().getTrackingController(), getStoreFactory().getConversationStore().getCurrentConversation(), SentPictureEvent.Source.CAMERA, SentPictureEvent.Method.FULL_SCREEN); extendedCursorContainer.close(true); break; } } private void sendVideo(Uri uri) { Timber.i(" uri.getPath %s", uri.getPath()); Timber.i(" uri %s", uri); AssetForUpload assetForUpload = AssetFactory.fromContentUri(uri); getStoreFactory().getConversationStore().sendMessage(assetForUpload, assetErrorHandlerVideo); getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); getStoreFactory().getInAppNotificationStore().setUserSendingPicture(false); extendedCursorContainer.close(true); } private void sendImage(Uri uri) { ImageAsset imageAsset = ImageAssetFactory.getImageAsset(uri); getStoreFactory().getConversationStore().sendMessage(imageAsset); } @Override public void onCanceled(AssetIntentsManager.IntentType type) { } @Override public void onFailed(AssetIntentsManager.IntentType type) { } @Override public void openIntent(Intent intent, AssetIntentsManager.IntentType intentType) { if (MediaStore.ACTION_VIDEO_CAPTURE.equals(intent.getAction()) && extendedCursorContainer.getType() == ExtendedCursorContainer.Type.IMAGES && extendedCursorContainer.isExpanded()) { // Close keyboard camera before requesting external camera for recording video extendedCursorContainer.close(true); } startActivityForResult(intent, intentType.requestCode); getActivity().overridePendingTransition(R.anim.camera_in, R.anim.camera_out); } @Override public void onPermissionFailed(AssetIntentsManager.IntentType type) { } @Override public void onPagerEnabledStateHasChanged(boolean enabled) { } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (positionOffset > 0) { extendedCursorContainer.close(true); } } @Override public void onPageSelected(int position) { } @Override public void onPageScrollStateChanged(int state) { } @Override public void onExtendedCursorClosed() { cursorLayout.onExtendedCursorClosed(); hideSendButtonIfNeeded(); } private void hideSendButtonIfNeeded() { if (!getControllerFactory().getUserPreferencesController().isCursorSendButtonEnabled() || TextUtils.isEmpty(cursorLayout.getText())) { cursorLayout.showSendButton(false); } } private void editMessage(final Message message) { if (cursorLayout == null) { return; } cursorLayout.editMessage(message); getControllerFactory().getConversationScreenController().setMessageBeingEdited(message); messageAdapter.notifyDataSetChanged(); // Add small delay so triggering keyboard works new Handler().postDelayed(new Runnable() { @Override public void run() { KeyboardUtils.showKeyboard(getActivity()); } }, 200); } private void deleteMessage(final Message message, final boolean forEveryone) { Dialog dialog = new AlertDialog.Builder(getContext()) .setTitle(forEveryone ? R.string.conversation__message_action__delete_for_everyone : R.string.conversation__message_action__delete_for_me) .setMessage(R.string.conversation__message_action__delete_details).setCancelable(true) .setNegativeButton(R.string.conversation__message_action__delete__dialog__cancel, null) .setPositiveButton(R.string.conversation__message_action__delete__dialog__ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (forEveryone) { message.recall(); } else { message.delete(); } Toast.makeText(getContext(), R.string.conversation__message_action__delete__confirmation, Toast.LENGTH_SHORT).show(); getControllerFactory().getTrackingController() .tagEvent(new DeletedMessageEvent(message, forEveryone)); } }) .create(); dialog.show(); } private void copyMessage(Message message) { getControllerFactory().getTrackingController() .tagEvent(new CopiedMessageEvent(message.getMessageType().name())); ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData .newPlainText(getContext().getString(R.string.conversation__action_mode__copy__description, message.getUser().getDisplayName()), message.getBody()); clipboard.setPrimaryClip(clip); Toast.makeText(getContext(), R.string.conversation__action_mode__copy__toast, Toast.LENGTH_SHORT).show(); } private void forwardMessage(final Message message) { getControllerFactory().getTrackingController() .tagEvent(new ForwardedMessageEvent(message.getMessageType().name())); final ShareCompat.IntentBuilder intentBuilder = ShareCompat.IntentBuilder.from(getActivity()); intentBuilder.setChooserTitle(R.string.conversation__action_mode__fwd__chooser__title); switch (message.getMessageType()) { case TEXT: case RICH_MEDIA: intentBuilder.setType("text/plain"); intentBuilder.setText(message.getBody()); intentBuilder.startChooser(); break; case ANY_ASSET: case VIDEO_ASSET: case AUDIO_ASSET: case ASSET: final ProgressDialog dialog = ProgressDialog.show(getContext(), getString(R.string.conversation__action_mode__fwd__dialog__title), getString(R.string.conversation__action_mode__fwd__dialog__message), true, true, null); // TODO: Once https://wearezeta.atlassian.net/browse/CM-976 is resolved, this 'if' block can be removed if (message.getMessageType() == Message.Type.ASSET) { final ImageAsset imageAsset = message.getImage(); intentBuilder.setType(imageAsset.getMimeType()); imageAsset.saveImageToGallery(new ImageAsset.SaveCallback() { @Override public void imageSaved(Uri uri) { if (getActivity() == null) { return; } dialog.dismiss(); intentBuilder.addStream(uri); intentBuilder.startChooser(); } @Override public void imageSavingFailed(Exception ex) { if (getActivity() == null) { return; } dialog.dismiss(); } }); } else { final Asset messageAsset = message.getAsset(); intentBuilder.setType(messageAsset.getMimeType()); messageAsset.getContentUri(new Asset.LoadCallback<Uri>() { @Override public void onLoaded(Uri uri) { if (getActivity() == null) { return; } dialog.dismiss(); intentBuilder.addStream(uri); intentBuilder.startChooser(); } @Override public void onLoadFailed() { if (getActivity() == null) { return; } dialog.dismiss(); } }); } break; } } private void saveMessage(Message message) { if (message.getMessageType() == Message.Type.ASSET) { imageAssetToSave = message.getImage(); if (PermissionUtils.hasSelfPermissions(getActivity(), SAVE_IMAGE_PERMISSIONS)) { saveImageAssetToGallery(); } else { ActivityCompat.requestPermissions(getActivity(), SAVE_IMAGE_PERMISSIONS, SAVE_IMAGE_PERMISSION_REQUEST_ID); } } else { final ProgressDialog dialog = ProgressDialog.show(getContext(), getString(R.string.conversation__action_mode__fwd__dialog__title), getString(R.string.conversation__action_mode__fwd__dialog__message), true, true, null); final Asset asset = message.getAsset(); asset.saveToDownloads(new Asset.LoadCallback() { @Override public void onLoaded(Object o) { if (getActivity() == null || getControllerFactory() == null || getControllerFactory().isTornDown()) { return; } getControllerFactory().getTrackingController() .tagEvent(new SavedFileEvent(asset.getMimeType(), (int) asset.getSizeInBytes())); Toast.makeText(getActivity(), com.waz.zclient.ui.R.string.content__file__action__save_completed, Toast.LENGTH_SHORT).show(); dialog.dismiss(); } @Override public void onLoadFailed() { if (getActivity() == null || getControllerFactory() == null || getControllerFactory().isTornDown()) { return; } Toast.makeText(getActivity(), com.waz.zclient.ui.R.string.content__file__action__save_error, Toast.LENGTH_SHORT).show(); dialog.dismiss(); } }); } } private void saveImageAssetToGallery() { if (imageAssetToSave == null) { return; } imageAssetToSave.saveImageToGallery(new ImageAsset.SaveCallback() { @Override public void imageSaved(Uri uri) { if (getControllerFactory() == null || getControllerFactory().isTornDown()) { return; } ((BaseScalaActivity) getActivity()).injectJava(ImageNotificationsController.class) .showImageSavedNotification(imageAssetToSave.getId(), uri); Toast.makeText(getContext(), R.string.message_bottom_menu_action_save_ok, Toast.LENGTH_SHORT) .show(); imageAssetToSave = null; } @Override public void imageSavingFailed(Exception ex) { unableToSaveImageNoPermissions(); } }); } private void unableToSaveImageNoPermissions() { imageAssetToSave = null; Toast.makeText(getContext(), R.string.message_bottom_menu_action_save_fail, Toast.LENGTH_SHORT).show(); } @Override public void onEmojiSelected(String emoji) { cursorLayout.appendText(emoji); getControllerFactory().getUserPreferencesController().addRecentEmoji(emoji); } @Override public void onTypingIndicatorVisibilityChanged(boolean visible) { if (visible) { cursorLayout.showTopbar(false); } else { cursorLayout.showTopbar(!listView.computeIsScrolledToBottom()); } } public interface Container { void onOpenUrl(String url); } }