/** * 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.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.waz.api.ConversationsList; import com.waz.api.IConversation; import com.waz.api.ImageAsset; import com.waz.api.Message; import com.waz.api.MessageContent; import com.waz.api.OtrClient; import com.waz.api.SyncState; import com.waz.api.User; import com.waz.api.UsersList; import com.waz.model.MessageData; import com.waz.zclient.BaseActivity; import com.waz.zclient.OnBackPressedListener; import com.waz.zclient.R; import com.waz.zclient.controllers.collections.CollectionsObserver; import com.waz.zclient.controllers.drawing.DrawingController; import com.waz.zclient.controllers.drawing.DrawingObserver; import com.waz.zclient.controllers.drawing.IDrawingController; import com.waz.zclient.controllers.location.LocationObserver; import com.waz.zclient.controllers.navigation.Page; import com.waz.zclient.controllers.tracking.events.group.AddedMemberToGroupEvent; import com.waz.zclient.controllers.tracking.events.group.CreatedGroupConversationEvent; import com.waz.zclient.conversation.CollectionController; import com.waz.zclient.conversation.CollectionFragment; import com.waz.zclient.core.api.scala.ModelObserver; import com.waz.zclient.core.controllers.tracking.events.media.SentPictureEvent; import com.waz.zclient.core.stores.connect.IConnectStore; import com.waz.zclient.core.stores.conversation.ConversationChangeRequester; import com.waz.zclient.core.stores.conversation.ConversationStoreObserver; import com.waz.zclient.core.stores.participants.ParticipantsStoreObserver; import com.waz.zclient.pages.BaseFragment; import com.waz.zclient.pages.main.conversation.controller.ConversationScreenControllerObserver; import com.waz.zclient.pages.main.conversation.controller.IConversationScreenController; import com.waz.zclient.pages.main.drawing.DrawingFragment; import com.waz.zclient.pages.main.participants.ParticipantFragment; import com.waz.zclient.pages.main.participants.SingleParticipantFragment; import com.waz.zclient.pages.main.participants.TabbedParticipantBodyFragment; import com.waz.zclient.pages.main.pickuser.PickUserFragment; import com.waz.zclient.pages.main.pickuser.controller.IPickUserController; import com.waz.zclient.pages.main.pickuser.controller.PickUserControllerScreenObserver; import com.waz.zclient.pages.main.profile.camera.CameraContext; import com.waz.zclient.pages.main.profile.camera.CameraFragment; import com.waz.zclient.tracking.GlobalTrackingController; import com.waz.zclient.ui.utils.KeyboardUtils; import com.waz.zclient.utils.LayoutSpec; import com.waz.zclient.utils.TrackingUtils; import com.waz.zclient.utils.ViewUtils; import com.waz.zclient.views.LoadingIndicatorView; import java.util.ArrayList; import java.util.List; public class ConversationManagerFragment extends BaseFragment<ConversationManagerFragment.Container> implements ParticipantFragment.Container, LikesListFragment.Container, OnBackPressedListener, ConversationScreenControllerObserver, DrawingObserver, ConversationStoreObserver, DrawingFragment.Container, CameraFragment.Container, PickUserFragment.Container, PickUserControllerScreenObserver, ParticipantsStoreObserver, LocationObserver, SingleParticipantFragment.Container, CollectionsObserver { public static final String TAG = ConversationManagerFragment.class.getName(); public static final float PARALLAX_FACTOR = .4f; private LoadingIndicatorView loadingIndicatorView; // doesn't need to be restored private int headerHeight; private boolean groupConversation; private User otherUser; private IPickUserController.Destination pickUserDestination; private final ModelObserver<IConversation> conversationModelObserver = new ModelObserver<IConversation>() { @Override public void updated(IConversation model) { groupConversation = model.getType() == IConversation.Type.GROUP; otherUser = groupConversation ? null : model.getOtherParticipant(); } }; public static ConversationManagerFragment newInstance() { return new ConversationManagerFragment(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_conversation_manager, container, false); if (savedInstanceState == null) { FragmentManager fragmentManager = getChildFragmentManager(); fragmentManager.beginTransaction() .add(R.id.fl__conversation_manager__message_list_container, ConversationFragment.newInstance(), ConversationFragment.TAG) .commit(); } loadingIndicatorView = ViewUtils.getView(view, R.id.liv__conversation_manager__loading_indicator); return view; } @Override public void onStart() { super.onStart(); getStoreFactory().getConversationStore().addConversationStoreObserver(this); getStoreFactory().getParticipantsStore().addParticipantsStoreObserver(this); getControllerFactory().getConversationScreenController().addConversationControllerObservers(this); getControllerFactory().getDrawingController().addDrawingObserver(this); getControllerFactory().getCameraController().addCameraActionObserver(this); getControllerFactory().getPickUserController().addPickUserScreenControllerObserver(this); IConversation currentConversation = getStoreFactory().getConversationStore().getCurrentConversation(); if (currentConversation != null) { getStoreFactory().getParticipantsStore().setCurrentConversation(currentConversation); } getControllerFactory().getLocationController().addObserver(this); getCollectionController().addObserver(this); } @Override public void onStop() { getControllerFactory().getLocationController().removeObserver(this); getControllerFactory().getPickUserController().removePickUserScreenControllerObserver(this); getControllerFactory().getCameraController().removeCameraActionObserver(this); getControllerFactory().getDrawingController().removeDrawingObserver(this); getControllerFactory().getConversationScreenController().removeConversationControllerObservers(this); getStoreFactory().getParticipantsStore().removeParticipantsStoreObserver(this); getStoreFactory().getConversationStore().removeConversationStoreObserver(this); getCollectionController().removeObserver(this); super.onStop(); } private CollectionController getCollectionController() { return ((BaseActivity) getActivity()).injectJava(CollectionController.class); } @Override public void onDestroyView() { loadingIndicatorView = null; super.onDestroyView(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); Fragment fragment = getChildFragmentManager().findFragmentByTag(CameraFragment.TAG); if (fragment != null) { fragment.onActivityResult(requestCode, resultCode, data); } } @Override public boolean onBackPressed() { Fragment fragment = getChildFragmentManager().findFragmentById(R.id.fl__conversation_manager__message_list_container); if (fragment instanceof OnBackPressedListener && ((OnBackPressedListener) fragment).onBackPressed()) { return true; } if (fragment instanceof ParticipantFragment) { getControllerFactory().getConversationScreenController().hideParticipants(true, false); return true; } if (fragment instanceof PickUserFragment) { getControllerFactory().getPickUserController().hidePickUser(getCurrentPickerDestination(), true); return true; } if (fragment instanceof LikesListFragment) { getChildFragmentManager().popBackStack(LikesListFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); return true; } if (getControllerFactory().getConversationScreenController().isShowingParticipant()) { getControllerFactory().getConversationScreenController().hideParticipants(true, false); return true; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // // Conversation Controller Notifications // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onShowParticipants(View anchorView, boolean isSingleConversation, boolean isMemberOfConversation, boolean showDeviceTabIfSingle) { if (LayoutSpec.isPhone(getContext())) { KeyboardUtils.hideKeyboard(getActivity()); } this.getControllerFactory().getOnboardingController().incrementParticipantsShowCount(); this.getControllerFactory().getNavigationController().setRightPage(Page.PARTICIPANT, TAG); getChildFragmentManager() .beginTransaction() .setCustomAnimations(R.anim.slide_in_from_bottom_pick_user, R.anim.open_new_conversation__thread_list_out, R.anim.open_new_conversation__thread_list_in, R.anim.slide_out_to_bottom_pick_user) .replace(R.id.fl__conversation_manager__message_list_container, ParticipantFragment.newInstance(IConnectStore.UserRequester.PARTICIPANTS, showDeviceTabIfSingle ? TabbedParticipantBodyFragment.DEVICE_PAGE : TabbedParticipantBodyFragment.USER_PAGE), ParticipantFragment.TAG) .addToBackStack(ParticipantFragment.TAG) .commit(); } @Override public void onHideParticipants(boolean backOrCloseButtonPressed, boolean hideByConversationChange, boolean isSingleConversation) { this.getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); getChildFragmentManager().popBackStack(ParticipantFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void onShowEditConversationName(boolean show) { } @Override public void setListOffset(int offset) { if (getContainer() == null || getControllerFactory().getNavigationController().getCurrentRightPage() == Page.PARTICIPANT) { return; } float fac = (PARALLAX_FACTOR * offset) / headerHeight; getControllerFactory().getNavigationController().setScreenOffsetYFactor(-fac); } @Override public void onHeaderViewMeasured(int participantHeaderHeight) { headerHeight = participantHeaderHeight; } @Override public void onScrollParticipantsList(int verticalOffset, boolean scrolledToBottom) { } @Override public void onConversationLoaded() { } @Override public void onShowUser(User user) { if (LayoutSpec.isPhone(getContext())) { KeyboardUtils.hideKeyboard(getActivity()); } } @Override public void onHideUser() { } @Override public void onAddPeopleToConversation() { } @Override public void onShowConversationMenu(@IConversationScreenController.ConversationMenuRequester int requester, IConversation conversation, View anchorView) { } @Override public void onShowOtrClient(OtrClient otrClient, User user) { } @Override public void onShowCurrentOtrClient() { } @Override public void onHideOtrClient() { } @Override public void onShowLikesList(Message message) { getChildFragmentManager() .beginTransaction() .setCustomAnimations(R.anim.slide_in_from_bottom_pick_user, R.anim.open_new_conversation__thread_list_out, R.anim.open_new_conversation__thread_list_in, R.anim.slide_out_to_bottom_pick_user) .replace(R.id.fl__conversation_manager__message_list_container, LikesListFragment.newInstance(message), LikesListFragment.TAG) .addToBackStack(LikesListFragment.TAG) .commit(); } ////////////////////////////////////////////////////////////////////////////////////////// // // ConversationStoreObserver // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onConversationListUpdated(@NonNull ConversationsList conversationsList) { } @Override public void onConversationListStateHasChanged(ConversationsList.ConversationsListState state) { } @Override public void onCurrentConversationHasChanged(IConversation fromConversation, IConversation toConversation, ConversationChangeRequester conversationChangerSender) { if (conversationChangerSender == ConversationChangeRequester.START_CONVERSATION || conversationChangerSender == ConversationChangeRequester.INCOMING_CALL || conversationChangerSender == ConversationChangeRequester.LEAVE_CONVERSATION || conversationChangerSender == ConversationChangeRequester.DELETE_CONVERSATION || conversationChangerSender == ConversationChangeRequester.BLOCK_USER) { if (getControllerFactory().getNavigationController().getCurrentRightPage() == Page.CAMERA && !fromConversation.getId().equals(toConversation.getId())) { getControllerFactory().getCameraController().closeCamera(CameraContext.MESSAGE); } getControllerFactory().getConversationScreenController().hideParticipants(false, (conversationChangerSender == ConversationChangeRequester.START_CONVERSATION)); closeLikesList(); } if (toConversation != null) { getStoreFactory().getParticipantsStore().setCurrentConversation(toConversation); conversationModelObserver.setAndUpdate(toConversation); } getCollectionController().closeCollection(); } @Override public void onConversationSyncingStateHasChanged(SyncState syncState) { } @Override public void onMenuConversationHasChanged(IConversation fromConversation) { } ////////////////////////////////////////////////////////////////////////////////////////// // // Camera callbacks // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onShowDrawing(ImageAsset image, DrawingController.DrawingDestination drawingDestination, IDrawingController.DrawingMethod method) { getChildFragmentManager().beginTransaction() .setCustomAnimations(R.anim.camera__from__profile__transition, R.anim.profile_fade_out_form, R.anim.profile_fade_in_form, R.anim.profile_fade_out_form) .replace(R.id.fl__conversation_manager__message_list_container, DrawingFragment.newInstance(image, drawingDestination, method), DrawingFragment.TAG) .addToBackStack(DrawingFragment.TAG) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.DRAWING, TAG); } @Override public void onHideDrawing(DrawingController.DrawingDestination drawingDestination, boolean imageSent) { switch (drawingDestination) { case CAMERA_PREVIEW_VIEW: getChildFragmentManager().popBackStack(DrawingFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); getControllerFactory().getCameraController().closeCamera(CameraContext.MESSAGE); getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); break; case SINGLE_IMAGE_VIEW: case SKETCH_BUTTON: getChildFragmentManager().popBackStack(DrawingFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); break; } } @Override public void openCollection() { getChildFragmentManager().beginTransaction() .setCustomAnimations(R.anim.slide_in_from_bottom_pick_user, R.anim.slide_out_to_bottom_pick_user, R.anim.slide_in_from_bottom_pick_user, R.anim.slide_out_to_bottom_pick_user) .replace(R.id.fl__conversation_manager__message_list_container, CollectionFragment.newInstance(), CollectionFragment.TAG()) .addToBackStack(CollectionFragment.TAG()) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.COLLECTION, TAG); } @Override public void closeCollection() { getChildFragmentManager().popBackStack(CollectionFragment.TAG(), FragmentManager.POP_BACK_STACK_INCLUSIVE); getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); } @Override public void shareCollectionItem(MessageData messageData) { } @Override public void closeCollectionShare() { } @Override public void nextItemRequested() { } @Override public void previousItemRequested() { } @Override public void dismissUserProfile() { dismissSingleUserProfile(); } @Override public void dismissSingleUserProfile() { getChildFragmentManager().popBackStackImmediate(); getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); } @Override public void showRemoveConfirmation(User user) { } @Override public void onBitmapSelected(ImageAsset imageAsset, boolean imageFromCamera, CameraContext cameraContext) { if (cameraContext != CameraContext.MESSAGE) { return; } getStoreFactory().getConversationStore().sendMessage(imageAsset); getStoreFactory().getNetworkStore().doIfHasInternetOrNotifyUser(null); // Photo sent via contacts quick menu TrackingUtils.onSentPhotoMessage(((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class), getStoreFactory().getConversationStore().getCurrentConversation(), imageFromCamera ? SentPictureEvent.Source.CAMERA : SentPictureEvent.Source.GALLERY, SentPictureEvent.Method.FULL_SCREEN); getControllerFactory().getCameraController().closeCamera(CameraContext.MESSAGE); } @Override public void onCameraNotAvailable() { } @Override public void onOpenCamera(CameraContext cameraContext) { if (cameraContext != CameraContext.MESSAGE) { return; } getChildFragmentManager().beginTransaction() .setCustomAnimations(R.anim.camera__from__message_stream_transition, R.anim.message_stream__to__camera_transition, R.anim.message_stream__from__camera_transition, R.anim.camera__to__message_stream__transition) .replace(R.id.fl__conversation_manager__message_list_container, CameraFragment.newInstance(CameraContext.MESSAGE), CameraFragment.TAG) .addToBackStack(CameraFragment.TAG) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.CAMERA, TAG); } @Override public void onCloseCamera(CameraContext cameraContext) { if (cameraContext != CameraContext.MESSAGE) { return; } getChildFragmentManager().popBackStackImmediate(); getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); } @Override public void onOpenUrl(String url) { getContainer().onOpenUrl(url); } @Override public void dismissDialog() { } @Override public void showIncomingPendingConnectRequest(IConversation conversation) { // noop } @Override public void onSelectedUsers(List<User> users, ConversationChangeRequester requester) { // TODO https://wearezeta.atlassian.net/browse/AN-3730 getControllerFactory().getPickUserController().hidePickUser(getCurrentPickerDestination(), false); IConversation currentConversation = getStoreFactory().getConversationStore().getCurrentConversation(); if (currentConversation.getType() == IConversation.Type.ONE_TO_ONE) { getStoreFactory().getConversationStore().createGroupConversation(users, requester); if (!getStoreFactory().getNetworkStore().hasInternetConnection()) { ViewUtils.showAlertDialog(getActivity(), R.string.conversation__create_group_conversation__no_network__title, R.string.conversation__create_group_conversation__no_network__message, R.string.conversation__create_group_conversation__no_network__button, null, true); } ((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new CreatedGroupConversationEvent(true, (users.size() + 1))); } else if (currentConversation.getType() == IConversation.Type.GROUP) { currentConversation.addMembers(users); if (!getStoreFactory().getNetworkStore().hasInternetConnection()) { ViewUtils.showAlertDialog(getActivity(), R.string.conversation__add_user__no_network__title, R.string.conversation__add_user__no_network__message, R.string.conversation__add_user__no_network__button, null, true); } ((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new AddedMemberToGroupEvent(getParticipantsCount(), users.size())); } } @Override public LoadingIndicatorView getLoadingViewIndicator() { return loadingIndicatorView; } @Override public IPickUserController.Destination getCurrentPickerDestination() { return pickUserDestination; } @Override public void onShowPickUser(IPickUserController.Destination destination, View anchorView) { if (!(destination.equals(IPickUserController.Destination.CURSOR) || destination.equals(IPickUserController.Destination.PARTICIPANTS))) { return; } pickUserDestination = destination; if (LayoutSpec.isPhone(getContext())) { KeyboardUtils.hideKeyboard(getActivity()); } getControllerFactory().getNavigationController().setRightPage(Page.PICK_USER_ADD_TO_CONVERSATION, TAG); if (!groupConversation && otherUser != null) { getControllerFactory().getPickUserController().addUser(otherUser); } getChildFragmentManager() .beginTransaction() .setCustomAnimations(R.anim.slide_in_from_bottom_pick_user, R.anim.open_new_conversation__thread_list_out, R.anim.open_new_conversation__thread_list_in, R.anim.slide_out_to_bottom_pick_user) .replace(R.id.fl__conversation_manager__message_list_container, PickUserFragment.newInstance(true, groupConversation), PickUserFragment.TAG) .addToBackStack(PickUserFragment.TAG) .commit(); } @Override public void onHidePickUser(IPickUserController.Destination destination, boolean closeWithoutSelectingPeople) { if (!destination.equals(getCurrentPickerDestination())) { return; } if (IPickUserController.Destination.CURSOR.equals(getCurrentPickerDestination())) { getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); } else { getControllerFactory().getNavigationController().setRightPage(Page.PARTICIPANT, TAG); } getChildFragmentManager().popBackStack(PickUserFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void onShowUserProfile(User user, View anchorView) { // noop } @Override public void onHideUserProfile() { // noop } // ParticipantStoreObserver @Override public void conversationUpdated(IConversation conversation) { } @Override public void participantsUpdated(UsersList participants) { ArrayList<String> participantIds = new ArrayList<>(); for (int i = 0; i < participants.size(); i++) { participantIds.add(participants.get(i).getId()); } // Exclude existing participants of conversation when adding people getStoreFactory().getPickUserStore().setExcludedUsers(participantIds.toArray(new String[participantIds.size()])); } @Override public void otherUserUpdated(User otherUser) { getStoreFactory().getPickUserStore().setExcludedUsers(new String[] {}); } private int getParticipantsCount() { return getStoreFactory().getConversationStore().getCurrentConversation().getUsers().size(); } @Override public void onShowShareLocation() { getChildFragmentManager().beginTransaction() .replace(R.id.fl__conversation_manager__message_list_container, LocationFragment.newInstance(), LocationFragment.TAG) .addToBackStack(LocationFragment.TAG) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.SHARE_LOCATION, TAG); } @Override public void onHideShareLocation(MessageContent.Location location) { if (location != null) { getStoreFactory().getConversationStore().sendMessage(location); } getControllerFactory().getNavigationController().setRightPage(Page.MESSAGE_STREAM, TAG); getChildFragmentManager().popBackStack(LocationFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void closeLikesList() { Fragment fragment = getChildFragmentManager().findFragmentById(R.id.fl__conversation_manager__message_list_container); if (fragment instanceof LikesListFragment) { getChildFragmentManager().popBackStack(LikesListFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } } public interface Container { void onOpenUrl(String url); } }