/** * 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.participants; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.LinearLayout; import com.waz.api.IConversation; import com.waz.api.Message; import com.waz.api.NetworkMode; import com.waz.api.OtrClient; import com.waz.api.User; import com.waz.api.UsersList; import com.waz.zclient.BaseActivity; import com.waz.zclient.R; import com.waz.zclient.controllers.accentcolor.AccentColorObserver; 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.confirmation.TwoButtonConfirmationCallback; import com.waz.zclient.controllers.tracking.events.group.LeaveGroupConversationEvent; import com.waz.zclient.controllers.tracking.events.group.OpenedGroupActionEvent; import com.waz.zclient.core.stores.connect.ConnectStoreObserver; import com.waz.zclient.core.stores.connect.IConnectStore; import com.waz.zclient.core.stores.conversation.ConversationChangeRequester; import com.waz.zclient.core.stores.network.NetworkAction; import com.waz.zclient.core.stores.participants.ParticipantsStoreObserver; import com.waz.zclient.media.SoundController; 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.participants.views.ParticipantsChatheadAdapter; import com.waz.zclient.pages.main.participants.views.ParticipantsGridView; import com.waz.zclient.pages.main.pickuser.controller.IPickUserController; import com.waz.zclient.tracking.GlobalTrackingController; import com.waz.zclient.ui.views.ZetaButton; import com.waz.zclient.utils.LayoutSpec; import com.waz.zclient.utils.ViewUtils; import com.waz.zclient.views.images.ImageAssetImageView; import com.waz.zclient.views.menus.FooterMenu; import com.waz.zclient.views.menus.FooterMenuCallback; public class ParticipantBodyFragment extends BaseFragment<ParticipantBodyFragment.Container> implements ConversationScreenControllerObserver, ParticipantsStoreObserver, AccentColorObserver, AdapterView.OnItemClickListener, ConnectStoreObserver { public static final String TAG = ParticipantBodyFragment.class.getName(); private static final String ARG_USER_REQUESTER = "ARG_USER_REQUESTER"; private ParticipantsGridView participantsGridView; private ParticipantsChatheadAdapter participantsAdapter; private FooterMenu footerMenu; private View topBorder; private LinearLayout footerWrapper; private ZetaButton unblockButton; private IConnectStore.UserRequester userRequester; private ImageAssetImageView imageAssetImageView; private int numberOfColumns; @Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { final Fragment parent = getParentFragment(); // Apply the workaround only if this is a child fragment, and the parent // is being removed. if (!enter && parent != null && parent.isRemoving()) { // This is a workaround for the bug where child fragments disappear when // the parent is removed (as all children are first removed from the parent) // See https://code.google.com/p/android/issues/detail?id=55228 Animation doNothingAnim = new AlphaAnimation(1, 1); doNothingAnim.setDuration(ViewUtils.getNextAnimationDuration(parent)); return doNothingAnim; } else { return super.onCreateAnimation(transit, enter, nextAnim); } } ////////////////////////////////////////////////////////////////////////////////////////// // // Lifecycle // ////////////////////////////////////////////////////////////////////////////////////////// public static ParticipantBodyFragment newInstance(IConnectStore.UserRequester userRequester) { ParticipantBodyFragment participantBodyFragment = new ParticipantBodyFragment(); Bundle args = new Bundle(); args.putSerializable(ARG_USER_REQUESTER, userRequester); participantBodyFragment.setArguments(args); return participantBodyFragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); userRequester = (IConnectStore.UserRequester) args.getSerializable(ARG_USER_REQUESTER); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_group_participant, viewGroup, false); footerMenu = ViewUtils.getView(view, R.id.fm__participants__footer); topBorder = ViewUtils.getView(view, R.id.v_participants__footer__top_border); footerWrapper = ViewUtils.getView(view, R.id.ll__participants__footer_wrapper); unblockButton = ViewUtils.getView(view, R.id.zb__single_user_participants__unblock_button); imageAssetImageView = ViewUtils.getView(view, R.id.iaiv__participant_body); imageAssetImageView.setDisplayType(ImageAssetImageView.DisplayType.CIRCLE); participantsAdapter = new ParticipantsChatheadAdapter(); participantsGridView = ViewUtils.getView(view, R.id.pgv__participants); participantsGridView.setAdapter(participantsAdapter); participantsGridView.setOnItemClickListener(this); participantsGridView.setSelector(getResources().getDrawable(R.drawable.transparent)); participantsGridView.setOnScrollListener(participantsGridOnScrollListener); numberOfColumns = getResources().getInteger(R.integer.participant_column__count); participantsGridView.setNumColumns(numberOfColumns); // Hide footer until conversation is loaded footerMenu.setVisibility(View.GONE); unblockButton.setVisibility(View.GONE); // Toggle color background view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getContainer().onClickedEmptyBackground(); } }); return view; } @Override public void onStart() { super.onStart(); if (userRequester == IConnectStore.UserRequester.POPOVER) { getStoreFactory().getConnectStore().addConnectRequestObserver(this); final User user = getStoreFactory().getSingleParticipantStore().getUser(); getStoreFactory().getConnectStore().loadUser(user.getId(), userRequester); } else { getStoreFactory().getParticipantsStore().addParticipantsStoreObserver(this); } getControllerFactory().getConversationScreenController().addConversationControllerObservers(this); getControllerFactory().getAccentColorController().addAccentColorObserver(this); } @Override public void onStop() { participantsAdapter.tearDown(); getStoreFactory().getConnectStore().removeConnectRequestObserver(this); getControllerFactory().getConversationScreenController().removeConversationControllerObservers(this); getStoreFactory().getParticipantsStore().removeParticipantsStoreObserver(this); getControllerFactory().getAccentColorController().removeAccentColorObserver(this); super.onStop(); } @Override public void onDestroyView() { imageAssetImageView = null; if (participantsGridView != null) { participantsGridView.setOnScrollListener(null); participantsGridView = null; } participantsAdapter = null; footerMenu = null; topBorder = null; footerWrapper = null; participantsGridView = null; super.onDestroyView(); } ////////////////////////////////////////////////////////////////////////////////////////// // // Notifications // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { User user = participantsAdapter.getItem(position); getControllerFactory().getConversationScreenController().showUser(user); } ////////////////////////////////////////////////////////////////////////////////////////// // // Conversation Manager Notifications // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onShowParticipants(View anchorView, boolean isSingleConversation, boolean isMemberOfConversation, boolean showDeviceTabIfSingle) { if (participantsGridView == null) { return; } if (isSingleConversation) { // Enable toggling of colour background for 1:1 conversations participantsGridView.setCallback(new ParticipantsGridView.Callback() { @Override public void onClicked() { getContainer().onClickedEmptyBackground(); } }); return; } participantsGridView.setCallback(null); } @Override public void onHideParticipants(boolean backOrButtonPressed, boolean hideByConversationChange, boolean isSingleConversation) { } @Override public void onShowEditConversationName(boolean show) { } @Override public void setListOffset(int offset) { } @Override public void onHeaderViewMeasured(int participantHeaderHeight) { } @Override public void onScrollParticipantsList(int verticalOffset, boolean scrolledToBottom) { if (topBorder == null) { return; } // Toggle footer border if (scrolledToBottom) { topBorder.setVisibility(View.INVISIBLE); } else { topBorder.setVisibility(View.VISIBLE); } } @Override public void onConversationLoaded() { } @Override public void onShowUser(User user) { } @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) { } @Override public void conversationUpdated(final IConversation conversation) { footerMenu.setVisibility(View.VISIBLE); if (conversation.getType() == IConversation.Type.ONE_TO_ONE) { footerMenu.setLeftActionText(getString(R.string.glyph__plus)); topBorder.setVisibility(View.INVISIBLE); footerMenu.setRightActionText(getString(R.string.glyph__more)); getStoreFactory() .getSingleParticipantStore() .setUser(conversation.getOtherParticipant()); } else { imageAssetImageView.setVisibility(View.GONE); // Check if self user is member for group conversation if (conversation.isMemberOfConversation()) { footerMenu.setLeftActionText(getString(R.string.glyph__add_people)); footerMenu.setRightActionText(getString(R.string.glyph__more)); footerMenu.setLeftActionLabelText(getString(R.string.conversation__action__add_people)); } else { footerMenu.setLeftActionText(""); footerMenu.setRightActionText(""); footerMenu.setLeftActionLabelText(""); } if (lastParticipantAboveFooter()) { topBorder.setVisibility(View.INVISIBLE); } else { topBorder.setVisibility(View.VISIBLE); } } footerMenu.setCallback(new FooterMenuCallback() { @Override public void onLeftActionClicked() { if (userRequester == IConnectStore.UserRequester.POPOVER) { final User user = getStoreFactory().getSingleParticipantStore().getUser(); if (user.isMe()) { getControllerFactory().getConversationScreenController().hideParticipants(true, false); // Go to conversation with this user getControllerFactory().getPickUserController().hidePickUserWithoutAnimations(getContainer().getCurrentPickerDestination()); getStoreFactory().getConversationStore().setCurrentConversation(user.getConversation(), ConversationChangeRequester.START_CONVERSATION); return; } } if (!conversation.isMemberOfConversation()) { return; } ((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedGroupActionEvent()); getControllerFactory().getConversationScreenController().addPeopleToConversation(); } @Override public void onRightActionClicked() { getStoreFactory().getNetworkStore().doIfHasInternetOrNotifyUser(new NetworkAction() { @Override public void execute(NetworkMode networkMode) { if (!conversation.isMemberOfConversation()) { return; } if (userRequester == IConnectStore.UserRequester.POPOVER) { User otherUser = conversation.getOtherParticipant(); getContainer().toggleBlockUser(otherUser, otherUser.getConnectionStatus() != User.ConnectionStatus.BLOCKED); } else { getControllerFactory().getConversationScreenController().showConversationMenu( IConversationScreenController.CONVERSATION_DETAILS, conversation, null); } } @Override public void onNoNetwork() { ViewUtils.showAlertDialog(getActivity(), R.string.alert_dialog__no_network__header, R.string.leave_conversation_failed__message, R.string.alert_dialog__confirmation, null, true); } }); } }); } @Override public void participantsUpdated(final UsersList participants) { participantsAdapter.setUsersList(participants, numberOfColumns); // Toggle footer border depending on if overlapping with participants new Handler().post(new Runnable() { @Override public void run() { if (topBorder == null) { return; } if (lastParticipantAboveFooter()) { topBorder.setVisibility(View.INVISIBLE); } else { topBorder.setVisibility(View.VISIBLE); } } }); } @Override public void otherUserUpdated(final User otherUser) { if (otherUser == null || getView() == null) { return; } participantsAdapter.setUsersList(null, 1); imageAssetImageView.setVisibility(View.VISIBLE); imageAssetImageView.connectImageAsset(otherUser.getPicture()); switch (otherUser.getConnectionStatus()) { case BLOCKED: footerMenu.setVisibility(View.GONE); unblockButton.setVisibility(View.VISIBLE); unblockButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { otherUser.unblock(); } }); break; default: unblockButton.setVisibility(View.GONE); unblockButton.setOnClickListener(null); footerMenu.setVisibility(View.VISIBLE); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // // Event listeners // ////////////////////////////////////////////////////////////////////////////////////////// private AbsListView.OnScrollListener participantsGridOnScrollListener = new AbsListView.OnScrollListener() { int currentScrollState = SCROLL_STATE_IDLE; @Override public void onScrollStateChanged(AbsListView absListView, int i) { currentScrollState = i; } @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (participantsGridView == null) { return; } if (currentScrollState != SCROLL_STATE_IDLE) { boolean scrolledToBottom = false; if (firstVisibleItem + visibleItemCount == totalItemCount) { scrolledToBottom = lastParticipantAboveFooter(); } //NPE getControllerFactory().getConversationScreenController().onScrollParticipantsList(participantsGridView.computeVerticalScrollOffset(), scrolledToBottom); } } }; private boolean lastParticipantAboveFooter() { if (participantsGridView == null) { return false; } if (participantsGridView.getLastVisiblePosition() < participantsGridView.getCount() - 1) { return false; } final int lastViewIndex = participantsGridView.getLastVisiblePosition() - participantsGridView.getFirstVisiblePosition(); if (lastViewIndex == -1) { return true; } int lastItemBottom = participantsGridView.getChildAt(lastViewIndex).getBottom(); int footerTop = footerWrapper.getTop(); return lastItemBottom <= footerTop; } ////////////////////////////////////////////////////////////////////////////////////////// // // AccentColorObserver // ////////////////////////////////////////////////////////////////////////////////////////// @Override public void onAccentColorHasChanged(Object sender, int color) { unblockButton.setAccentColor(color); } @Override public void onConnectUserUpdated(final User user, IConnectStore.UserRequester usertype) { if (usertype != userRequester || user == null) { return; } imageAssetImageView.setVisibility(View.VISIBLE); imageAssetImageView.connectImageAsset(user.getPicture()); footerMenu.setVisibility(View.VISIBLE); topBorder.setVisibility(View.INVISIBLE); final IConversation conversation = getStoreFactory().getConversationStore().getCurrentConversation(); if (conversation.getType() == IConversation.Type.ONE_TO_ONE) { if (user.isMe()) { footerMenu.setLeftActionText(getString(R.string.glyph__profile)); footerMenu.setLeftActionLabelText(getString(R.string.popover__action__profile)); footerMenu.setRightActionText(""); footerMenu.setRightActionLabelText(""); } else { footerMenu.setLeftActionText(getString(R.string.glyph__add_people)); footerMenu.setLeftActionLabelText(getString(R.string.conversation__action__create_group)); footerMenu.setRightActionText(getString(R.string.glyph__block)); footerMenu.setRightActionLabelText(getString(R.string.popover__action__block)); } } else { if (user.isMe()) { footerMenu.setLeftActionText(getString(R.string.glyph__profile)); footerMenu.setLeftActionLabelText(getString(R.string.popover__action__profile)); footerMenu.setRightActionText(getString(R.string.glyph__minus)); footerMenu.setRightActionLabelText(""); } else { footerMenu.setLeftActionText(getString(R.string.glyph__conversation)); footerMenu.setLeftActionLabelText(getString(R.string.popover__action__open)); footerMenu.setRightActionText(getString(R.string.glyph__minus)); footerMenu.setRightActionLabelText(getString(R.string.popover__action__remove)); } } footerMenu.setCallback(new FooterMenuCallback() { @Override public void onLeftActionClicked() { if (user.isMe() || conversation.getType() != IConversation.Type.ONE_TO_ONE) { getControllerFactory().getConversationScreenController().hideParticipants(true, false); // Go to conversation with this user getControllerFactory().getPickUserController().hidePickUserWithoutAnimations(getContainer().getCurrentPickerDestination()); getStoreFactory().getConversationStore().setCurrentConversation(user.getConversation(), ConversationChangeRequester.START_CONVERSATION); } else { ((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new OpenedGroupActionEvent()); getControllerFactory().getConversationScreenController().addPeopleToConversation(); } } @Override public void onRightActionClicked() { if (conversation.getType() == IConversation.Type.ONE_TO_ONE) { if (!user.isMe()) { getContainer().toggleBlockUser(user, user.getConnectionStatus() != User.ConnectionStatus.BLOCKED); } } else { getStoreFactory().getNetworkStore().doIfHasInternetOrNotifyUser(new NetworkAction() { @Override public void execute(NetworkMode networkMode) { if (user.isMe()) { showLeaveConfirmation(getStoreFactory().getConversationStore().getCurrentConversation()); } else { getContainer().showRemoveConfirmation(user); } } @Override public void onNoNetwork() { if (user.isMe()) { ViewUtils.showAlertDialog(getActivity(), R.string.alert_dialog__no_network__header, R.string.leave_conversation_failed__message, R.string.alert_dialog__confirmation, null, true); } else { ViewUtils.showAlertDialog(getActivity(), R.string.alert_dialog__no_network__header, R.string.remove_from_conversation__no_network__message, R.string.alert_dialog__confirmation, null, true); } } }); } } }); } @Override public void onInviteRequestSent(IConversation conversation) { } private void showLeaveConfirmation(final IConversation conversation) { ConfirmationCallback callback = new TwoButtonConfirmationCallback() { @Override public void positiveButtonClicked(boolean checkboxIsSelected) { if (getStoreFactory() == null || getControllerFactory() == null || getStoreFactory().isTornDown() || getControllerFactory().isTornDown()) { return; } ((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new LeaveGroupConversationEvent(true, getStoreFactory().getConversationStore().getCurrentConversation().getUsers().size())); getStoreFactory().getConversationStore().leave(conversation); getStoreFactory().getConversationStore().setCurrentConversationToNext( ConversationChangeRequester.LEAVE_CONVERSATION); if (LayoutSpec.isTablet(getActivity())) { getControllerFactory().getConversationScreenController().hideParticipants(false, true); } } @Override public void negativeButtonClicked() { if (getControllerFactory() == null || getControllerFactory().isTornDown()) { return; } ((BaseActivity) getActivity()).injectJava(GlobalTrackingController.class).tagEvent(new LeaveGroupConversationEvent(false, getStoreFactory().getConversationStore().getCurrentConversation().getUsers().size())); } @Override public void onHideAnimationEnd(boolean confirmed, boolean canceled, boolean checkboxIsSelected) { } }; String header = getString(R.string.confirmation_menu__meta_remove); String text = getString(R.string.confirmation_menu__meta_remove_text); String confirm = getString(R.string.confirmation_menu__confirm_leave); String cancel = getString(R.string.confirmation_menu__cancel); String checkboxLabel = getString(R.string.confirmation_menu__delete_conversation__checkbox__label); ConfirmationRequest request = new ConfirmationRequest.Builder() .withHeader(header) .withMessage(text) .withPositiveButton(confirm) .withNegativeButton(cancel) .withConfirmationCallback(callback) .withCheckboxLabel(checkboxLabel) .withWireTheme(getControllerFactory().getThemeController().getThemeDependentOptionsTheme()) .withCheckboxSelectedByDefault() .build(); getControllerFactory().getConfirmationController().requestConfirmation(request, IConfirmationController.PARTICIPANTS); SoundController ctrl = inject(SoundController.class); if (ctrl != null) { ctrl.playAlert(); } } public interface Container { void onClickedEmptyBackground(); void toggleBlockUser(User otherUser, boolean block); void showRemoveConfirmation(User user); IPickUserController.Destination getCurrentPickerDestination(); } }