/** * 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; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.waz.api.ErrorsList; import com.waz.api.IConversation; import com.waz.api.Message; import com.waz.api.User; import com.waz.model.MessageData; import com.waz.utils.wrappers.URI; import com.waz.zclient.BaseActivity; import com.waz.zclient.OnBackPressedListener; import com.waz.zclient.R; import com.waz.zclient.controllers.accentcolor.AccentColorObserver; import com.waz.zclient.controllers.collections.CollectionsObserver; import com.waz.zclient.controllers.confirmation.ConfirmationObserver; import com.waz.zclient.controllers.confirmation.ConfirmationRequest; import com.waz.zclient.controllers.confirmation.IConfirmationController; import com.waz.zclient.controllers.giphy.GiphyObserver; import com.waz.zclient.controllers.navigation.Page; import com.waz.zclient.controllers.onboarding.OnboardingControllerObserver; import com.waz.zclient.controllers.singleimage.SingleImageObserver; import com.waz.zclient.conversation.CollectionController; import com.waz.zclient.conversation.CollectionFragment; import com.waz.zclient.conversation.ShareToMultipleFragment; import com.waz.zclient.core.stores.inappnotification.InAppNotificationStoreObserver; import com.waz.zclient.core.stores.inappnotification.KnockingEvent; import com.waz.zclient.fragments.ImageFragment; import com.waz.zclient.pages.BaseFragment; import com.waz.zclient.pages.main.conversation.SingleImageUserFragment; import com.waz.zclient.pages.main.conversation.VideoPlayerFragment; import com.waz.zclient.pages.main.conversationlist.ConfirmationFragment; import com.waz.zclient.pages.main.conversationpager.ConversationPagerFragment; import com.waz.zclient.pages.main.giphy.GiphySharingPreviewFragment; import com.waz.zclient.pages.main.onboarding.OnBoardingHintFragment; import com.waz.zclient.pages.main.onboarding.OnBoardingHintType; import com.waz.zclient.utils.SyncErrorUtils; import com.waz.zclient.utils.ViewUtils; import com.waz.zclient.views.menus.ConfirmationMenu; import net.hockeyapp.android.ExceptionHandler; public class MainPhoneFragment extends BaseFragment<MainPhoneFragment.Container> implements OnBackPressedListener, ConversationPagerFragment.Container, OnBoardingHintFragment.Container, OnboardingControllerObserver, SingleImageObserver, SingleImageUserFragment.Container, GiphyObserver, ConfirmationObserver, AccentColorObserver, CollectionsObserver, ConfirmationFragment.Container, InAppNotificationStoreObserver { public static final String TAG = MainPhoneFragment.class.getName(); private ConfirmationMenu confirmationMenu; //////////////////////////////////////////////////////////////////////////////////////////////////////// // // LifeCycle // //////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_main, container, false); if (savedInstanceState == null) { getChildFragmentManager().beginTransaction().replace(R.id.fl_fragment_main_content, ConversationPagerFragment.newInstance(), ConversationPagerFragment.TAG).commit(); } confirmationMenu = ViewUtils.getView(view, R.id.cm__confirm_action_light); confirmationMenu.setVisibility(View.GONE); return view; } @Override public void onStart() { super.onStart(); getStoreFactory().getInAppNotificationStore().addInAppNotificationObserver(this); getControllerFactory().getSingleImageController().addSingleImageObserver(this); getControllerFactory().getOnboardingController().addOnboardingControllerObserver(this); getControllerFactory().getGiphyController().addObserver(this); getControllerFactory().getConfirmationController().addConfirmationObserver(this); getControllerFactory().getAccentColorController().addAccentColorObserver(this); getCollectionController().addObserver(this); OnBoardingHintFragment fragment = (OnBoardingHintFragment) getChildFragmentManager().findFragmentByTag( OnBoardingHintFragment.TAG); if (fragment != null) { getControllerFactory().getOnboardingController().setCurrentHintType(fragment.getOnBoardingHintType()); } } @Override public void onStop() { getControllerFactory().getGiphyController().removeObserver(this); getControllerFactory().getSingleImageController().removeSingleImageObserver(this); getControllerFactory().getOnboardingController().removeOnboardingControllerObserver(this); getControllerFactory().getConfirmationController().removeConfirmationObserver(this); getControllerFactory().getAccentColorController().removeAccentColorObserver(this); getStoreFactory().getInAppNotificationStore().removeInAppNotificationObserver(this); getCollectionController().removeObserver(this); super.onStop(); } @Override public void onDestroyView() { confirmationMenu = null; super.onDestroyView(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); getChildFragmentManager().findFragmentById(R.id.fl_fragment_main_content).onActivityResult(requestCode, resultCode, data); } private CollectionController getCollectionController() { return ((BaseActivity) getActivity()).injectJava(CollectionController.class); } //////////////////////////////////////////////////////////////////////////////////////////////////////// // // Notifications // //////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public boolean onBackPressed() { if (confirmationMenu.getVisibility() == View.VISIBLE) { confirmationMenu.animateToShow(false); return true; } // Clear any overlays dismissOnBoardingHint(OnBoardingHintType.NONE); if (getChildFragmentManager().getBackStackEntryCount() > 0) { Fragment topFragment = getChildFragmentManager().findFragmentByTag(getChildFragmentManager().getBackStackEntryAt( getChildFragmentManager().getBackStackEntryCount() - 1).getName()); if (topFragment instanceof SingleImageUserFragment) { return ((SingleImageUserFragment) topFragment).onBackPressed(); } else if (topFragment instanceof VideoPlayerFragment) { return ((VideoPlayerFragment) topFragment).onBackPressed(); } else if (topFragment instanceof GiphySharingPreviewFragment) { if (!((GiphySharingPreviewFragment) topFragment).onBackPressed()) { getChildFragmentManager().popBackStackImmediate(GiphySharingPreviewFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } return true; } else if (topFragment instanceof CollectionFragment) { return ((CollectionFragment) topFragment).onBackPressed(); } else if (topFragment instanceof ConfirmationFragment) { return ((ConfirmationFragment) topFragment).onBackPressed(); } else if (topFragment instanceof ImageFragment) { if (!((ImageFragment) topFragment).onBackPressed()) { getChildFragmentManager().popBackStackImmediate(ImageFragment.TAG(), FragmentManager.POP_BACK_STACK_INCLUSIVE); } return true; } } // Back press is first delivered to the notification fragment, and if it's not consumed there, // it's then delivered to the main content. Fragment fragment = getChildFragmentManager().findFragmentById(R.id.fl_fragment_main_content); if (fragment instanceof OnBackPressedListener && ((OnBackPressedListener) fragment).onBackPressed()) { return true; } fragment = getChildFragmentManager().findFragmentById(R.id.fl__overlay_container); if (fragment instanceof OnBackPressedListener && ((OnBackPressedListener) fragment).onBackPressed()) { return true; } return getChildFragmentManager().popBackStackImmediate(); } @Override public void onOpenUrl(String url) { getContainer().onOpenUrl(url); } ////////////////////////////////////////////////////////////////////////////////////////// // // OnBoardingHintFragment // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void dismissOnBoardingHint(OnBoardingHintType requestedType) { getControllerFactory().getOnboardingController().hideOnboardingHint(requestedType); } @Override public void onShowOnboardingHint(final OnBoardingHintType hintType, int delayMilSec) { if (hintType == OnBoardingHintType.NONE) { return; } new Handler(Looper.myLooper()).postDelayed(new Runnable() { @Override public void run() { if (getContainer() == null || !isResumed()) { return; } // Additional check if hint types match. Some animations go through conversation list and might trigger pull down hint Page currentPage = getControllerFactory().getNavigationController().getCurrentPage(); IConversation currentConversation = getStoreFactory().getConversationStore().getCurrentConversation(); boolean currentConversationHasDraft = TextUtils.isEmpty(getStoreFactory().getDraftStore().getDraft( getStoreFactory().getConversationStore().getCurrentConversation())); OnBoardingHintType currentHintType = getControllerFactory().getOnboardingController().getCurrentOnBoardingHint( currentPage, currentConversation, currentConversationHasDraft); if (hintType != currentHintType) { return; } getChildFragmentManager().popBackStackImmediate(OnBoardingHintFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); getChildFragmentManager() .beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.slide_out_to_top, R.anim.slide_in_from_bottom, R.anim.fade_out) .add(R.id.fl_fragment_main_onboarding, OnBoardingHintFragment.newInstance(hintType), OnBoardingHintFragment.TAG) .addToBackStack(OnBoardingHintFragment.TAG) .commit(); } }, delayMilSec); } @Override public void onHideOnboardingHint(OnBoardingHintType type) { getChildFragmentManager().popBackStackImmediate(OnBoardingHintFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void onShowSingleImage(Message message) { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, ImageFragment.newInstance(message.getId()), ImageFragment.TAG()) .addToBackStack(ImageFragment.TAG()) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.SINGLE_MESSAGE, TAG); } @Override public void onShowUserImage(User user) { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, SingleImageUserFragment.newInstance(user), SingleImageUserFragment.TAG) .addToBackStack(SingleImageUserFragment.TAG) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.SINGLE_MESSAGE, TAG); } @Override public void onHideSingleImage() { } @Override public void onShowVideo(URI uri) { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, VideoPlayerFragment.newInstance(uri), VideoPlayerFragment.TAG) .addToBackStack(VideoPlayerFragment.TAG) .commit(); getControllerFactory().getNavigationController().setRightPage(Page.SINGLE_MESSAGE, TAG); } @Override public void onHideVideo() { getChildFragmentManager().popBackStackImmediate(VideoPlayerFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void onSearch(String keyword) { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, GiphySharingPreviewFragment.newInstance(keyword), GiphySharingPreviewFragment.TAG) .addToBackStack(GiphySharingPreviewFragment.TAG) .commit(); } @Override public void onRandomSearch() { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, GiphySharingPreviewFragment.newInstance(), GiphySharingPreviewFragment.TAG) .addToBackStack(GiphySharingPreviewFragment.TAG) .commit(); } @Override public void onTrendingSearch() { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, GiphySharingPreviewFragment.newInstance(), GiphySharingPreviewFragment.TAG) .addToBackStack(GiphySharingPreviewFragment.TAG) .commit(); } @Override public void onCloseGiphy() { getChildFragmentManager().popBackStackImmediate(GiphySharingPreviewFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void onCancelGiphy() { getChildFragmentManager().popBackStackImmediate(GiphySharingPreviewFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); } ////////////////////////////////////////////////////////////////////////////////////////// // // CollectionsObserver // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void openCollection() { } @Override public void closeCollection() { } @Override public void shareCollectionItem(MessageData messageData) { getChildFragmentManager().beginTransaction() .add(R.id.fl__overlay_container, ShareToMultipleFragment.newInstance(messageData.id()), ShareToMultipleFragment.TAG()) .addToBackStack(ShareToMultipleFragment.TAG()) .commit(); } @Override public void closeCollectionShare() { getChildFragmentManager().popBackStackImmediate(ShareToMultipleFragment.TAG(), FragmentManager.POP_BACK_STACK_INCLUSIVE); } @Override public void nextItemRequested() { } @Override public void previousItemRequested() { } ////////////////////////////////////////////////////////////////////////////////////////// // // ConfirmationObserver // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onRequestConfirmation(ConfirmationRequest confirmationRequest, @IConfirmationController.ConfirmationMenuRequester int requester) { confirmationMenu.onRequestConfirmation(confirmationRequest); } ////////////////////////////////////////////////////////////////////////////////////////// // // AccentColorObserver // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onAccentColorHasChanged(Object sender, int color) { if (getView() == null) { return; } confirmationMenu.setButtonColor(color); } @Override public void onIncomingMessage(Message message) { // ignore } @Override public void onIncomingKnock(KnockingEvent knock) { // ignore } @Override public void onSyncError(ErrorsList.ErrorDescription error) { if (getActivity() == null) { return; } switch (error.getType()) { case CANNOT_ADD_UNCONNECTED_USER_TO_CONVERSATION: case CANNOT_ADD_USER_TO_FULL_CONVERSATION: case CANNOT_CREATE_GROUP_CONVERSATION_WITH_UNCONNECTED_USER: getChildFragmentManager().beginTransaction() .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out) .replace(R.id.fl_dialog_container, ConfirmationFragment.newMessageOnlyInstance(getResources().getString(R.string.in_app_notification__sync_error__create_group_convo__title), SyncErrorUtils.getGroupErrorMessage(getContext(), error), getResources().getString(R.string.in_app_notification__sync_error__create_convo__button), error.getId()), ConfirmationFragment.TAG ) .addToBackStack(ConfirmationFragment.TAG) .commit(); break; case CANNOT_ADD_USER_TO_FULL_CALL: case CANNOT_CALL_CONVERSATION_WITH_TOO_MANY_MEMBERS: case CANNOT_SEND_VIDEO: case PLAYBACK_FAILURE: ExceptionHandler.saveException(new RuntimeException("Unhandled error " + error.getType()), null); break; case CANNOT_SEND_MESSAGE_TO_UNVERIFIED_CONVERSATION: case RECORDING_FAILURE: case CANNOT_SEND_ASSET_FILE_NOT_FOUND: case CANNOT_SEND_ASSET_TOO_LARGE: // Handled in ConversationFragment break; default: ExceptionHandler.saveException(new RuntimeException("Unexpected error " + error.getType()), null); } } @Override public void onDialogConfirm(String dialogId) { getChildFragmentManager().popBackStackImmediate(ConfirmationFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); dismissError(dialogId); } @Override public void onDialogCancel(String dialogId) { getChildFragmentManager().popBackStackImmediate(ConfirmationFragment.TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE); dismissError(dialogId); } private void dismissError(String errorId) { if (getActivity() == null || getStoreFactory().isTornDown()) { return; } getStoreFactory().getInAppNotificationStore().dismissError(errorId); } public interface Container { void onOpenUrl(String url); } }