/* Copyright © 2013-2014, Silent Circle, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Any redistribution, use, or modification is done solely for personal benefit and not for any commercial purpose or for monetary gain * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Silent Circle nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.silentcircle.contacts.group; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.silentcircle.contacts.ContactPhotoManager; import com.silentcircle.contacts.GroupMemberLoader; import com.silentcircle.contacts.GroupMetaDataLoader; import com.silentcircle.contacts.list.ContactTileAdapter; import com.silentcircle.contacts.list.ContactTileView; import com.silentcircle.contacts.model.account.AccountType; import com.silentcircle.contacts.R; import com.silentcircle.contacts.interactions.GroupDeletionDialogFragment; import com.silentcircle.contacts.model.AccountTypeManager; /** * Displays the details of a group and shows a list of actions possible for the group. */ public class GroupDetailFragment extends SherlockFragment implements OnScrollListener { public static interface Listener { /** * The group title has been loaded */ public void onGroupTitleUpdated(String title); /** * The number of group members has been determined */ public void onGroupSizeUpdated(String size); /** * User decided to go to Edit-Mode */ public void onEditRequested(Uri groupUri); /** * Contact is selected and should launch details page */ public void onContactSelected(Uri contactUri); } private static final String TAG = "GroupDetailFragment"; private static final int LOADER_METADATA = 0; private static final int LOADER_MEMBERS = 1; private Context mContext; private View mRootView; private TextView mGroupTitle; private TextView mGroupSize; private ListView mMemberListView; private View mEmptyView; private Listener mListener; private ContactTileAdapter mAdapter; private ContactPhotoManager mPhotoManager; private AccountTypeManager mAccountTypeManager; private Uri mGroupUri; private long mGroupId; private String mGroupName; private boolean mIsReadOnly; // private boolean mShowGroupActionInActionBar; private boolean mOptionsMenuGroupDeletable; private boolean mOptionsMenuGroupPresent; private boolean mCloseActivityAfterDelete; public GroupDetailFragment() { } @Override public void onAttach(Activity activity) { super.onAttach(activity); mContext = activity; mAccountTypeManager = AccountTypeManager.getInstance(mContext); Resources res = getResources(); int columnCount = res.getInteger(R.integer.contact_tile_column_count); mAdapter = new ContactTileAdapter(activity, mContactTileListener, columnCount, ContactTileAdapter.DisplayType.GROUP_MEMBERS); configurePhotoLoader(); } @Override public void onDetach() { super.onDetach(); mContext = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { setHasOptionsMenu(true); mRootView = inflater.inflate(R.layout.group_detail_fragment, container, false); mGroupTitle = (TextView) mRootView.findViewById(R.id.group_title); mGroupSize = (TextView) mRootView.findViewById(R.id.group_size); mEmptyView = mRootView.findViewById(android.R.id.empty); mMemberListView = (ListView) mRootView.findViewById(android.R.id.list); mMemberListView.setItemsCanFocus(true); mMemberListView.setAdapter(mAdapter); return mRootView; } public void loadGroup(Uri groupUri) { mGroupUri= groupUri; startGroupMetadataLoader(); } public void setQuickContact(boolean enableQuickContact) { mAdapter.enableQuickContact(enableQuickContact); } private void configurePhotoLoader() { if (mContext != null) { if (mPhotoManager == null) { mPhotoManager = ContactPhotoManager.getInstance(mContext); } if (mMemberListView != null) { mMemberListView.setOnScrollListener(this); } if (mAdapter != null) { mAdapter.setPhotoLoader(mPhotoManager); } } } public void setListener(Listener value) { mListener = value; } public Uri getGroupUri() { return mGroupUri; } /** * Start the loader to retrieve the metadata for this group. */ private void startGroupMetadataLoader() { getLoaderManager().restartLoader(LOADER_METADATA, null, mGroupMetadataLoaderListener); } /** * Start the loader to retrieve the list of group members. */ private void startGroupMembersLoader() { getLoaderManager().restartLoader(LOADER_MEMBERS, null, mGroupMemberListLoaderListener); } private final ContactTileView.Listener mContactTileListener = new ContactTileView.Listener() { @Override public void onContactSelected(Uri contactUri, Rect targetRect) { mListener.onContactSelected(contactUri); } @Override public void onCallNumberDirectly(String phoneNumber) { // No need to call phone number directly from People app. Log.w(TAG, "unexpected invocation of onCallNumberDirectly()"); } @Override public int getApproximateTileWidth() { return getView().getWidth() / mAdapter.getColumnCount(); } }; /** * The listener for the group metadata loader. */ private final LoaderManager.LoaderCallbacks<Cursor> mGroupMetadataLoaderListener = new LoaderManager.LoaderCallbacks<Cursor>() { @Override public CursorLoader onCreateLoader(int id, Bundle args) { return new GroupMetaDataLoader(mContext, mGroupUri); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { data.moveToPosition(-1); if (data.moveToNext()) { boolean deleted = data.getInt(GroupMetaDataLoader.DELETED) == 1; if (!deleted) { bindGroupMetaData(data); // Retrieve the list of members startGroupMembersLoader(); return; } } updateSize(-1); updateTitle(null); } @Override public void onLoaderReset(Loader<Cursor> loader) {} }; /** * The listener for the group members list loader */ private final LoaderManager.LoaderCallbacks<Cursor> mGroupMemberListLoaderListener = new LoaderManager.LoaderCallbacks<Cursor>() { @Override public CursorLoader onCreateLoader(int id, Bundle args) { return GroupMemberLoader.constructLoaderForGroupDetailQuery(mContext, mGroupId); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { updateSize(data.getCount()); mAdapter.setContactCursor(data); mMemberListView.setEmptyView(mEmptyView); } @Override public void onLoaderReset(Loader<Cursor> loader) {} }; private void bindGroupMetaData(Cursor cursor) { cursor.moveToPosition(-1); if (cursor.moveToNext()) { mGroupId = cursor.getLong(GroupMetaDataLoader.GROUP_ID); mGroupName = cursor.getString(GroupMetaDataLoader.TITLE); mIsReadOnly = cursor.getInt(GroupMetaDataLoader.IS_READ_ONLY) == 1; updateTitle(mGroupName); // Must call invalidate so that the option menu will get updated getActivity().supportInvalidateOptionsMenu (); } } private void updateTitle(String title) { if (mGroupTitle != null) { mGroupTitle.setText(title); } else { mListener.onGroupTitleUpdated(title); } } /** * Display the count of the number of group members. * @param size of the group (can be -1 if no size could be determined) */ private void updateSize(int size) { String groupSizeString; if (size == -1) { groupSizeString = null; } else { String groupSizeTemplateString = getResources().getQuantityString(R.plurals.num_contacts_in_group, size); AccountType accountType = mAccountTypeManager.getAccountType(); groupSizeString = String.format(groupSizeTemplateString, size, accountType.getDisplayLabel(mContext)); } if (mGroupSize != null) { mGroupSize.setText(groupSizeString); } else { mListener.onGroupSizeUpdated(groupSizeString); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == OnScrollListener.SCROLL_STATE_FLING) { mPhotoManager.pause(); } else { mPhotoManager.resume(); } } @Override public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.view_group, menu); } public boolean isOptionsMenuChanged() { return mOptionsMenuGroupDeletable != isGroupDeletable() && mOptionsMenuGroupPresent != isGroupPresent(); } public boolean isGroupDeletable() { return mGroupUri != null && !mIsReadOnly; } public boolean isGroupPresent() { return mGroupUri != null; } @Override public void onPrepareOptionsMenu(Menu menu) { mOptionsMenuGroupDeletable = isGroupDeletable() && isVisible(); mOptionsMenuGroupPresent = isGroupPresent() && isVisible(); final MenuItem editMenu = menu.findItem(R.id.menu_edit_group); editMenu.setVisible(mOptionsMenuGroupPresent); final MenuItem deleteMenu = menu.findItem(R.id.menu_delete_group); deleteMenu.setVisible(mOptionsMenuGroupDeletable); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_edit_group: { if (mListener != null) mListener.onEditRequested(mGroupUri); break; } case R.id.menu_delete_group: { GroupDeletionDialogFragment.show(getFragmentManager(), mGroupId, mGroupName, mCloseActivityAfterDelete); return true; } } return false; } public void closeActivityAfterDelete(boolean closeActivity) { mCloseActivityAfterDelete = closeActivity; } public long getGroupId() { return mGroupId; } }