/* * Copyright 2014 Google Inc. All rights reserved. * * 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.google.samples.apps.iosched.ui; import android.app.AlertDialog; import android.content.*; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.RemoteException; import android.app.*; import android.content.CursorLoader; import android.content.Loader; import android.widget.CursorAdapter; import android.widget.PopupMenu; import android.text.TextUtils; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import com.google.samples.apps.iosched.R; import com.google.samples.apps.iosched.provider.ScheduleContract; import com.google.samples.apps.iosched.ui.widget.CollectionView; import com.google.samples.apps.iosched.ui.widget.CollectionViewCallbacks; import com.google.samples.apps.iosched.util.AccountUtils; import com.google.samples.apps.iosched.util.ImageLoader; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.plus.People; import com.google.android.gms.plus.Plus; import com.google.android.gms.plus.model.people.Person; import com.google.android.gms.plus.model.people.PersonBuffer; import com.google.samples.apps.iosched.util.UIUtils; import java.util.ArrayList; import static com.google.samples.apps.iosched.util.LogUtils.LOGD; import static com.google.samples.apps.iosched.util.LogUtils.makeLogTag; public class PeopleIveMetFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<People.LoadPeopleResult>, View.OnClickListener { private static final String TAG = makeLogTag(PeopleIveMetFragment.class); private final ArrayList<String> mPlusIds = new ArrayList<String>(); private CollectionView mCollectionView; private GoogleApiClient mApiClient; private View mApiError; private boolean mCallingApi; public static PeopleIveMetFragment newInstance() { return new PeopleIveMetFragment(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_people_ive_met, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { mCollectionView = (CollectionView) view.findViewById(R.id.collection_view); mApiError = view.findViewById(R.id.api_error); view.findViewById(R.id.retry).setOnClickListener(this); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Activity activity = getActivity(); if (!activity.isFinishing()) { // Start loading data LoaderManager loaderManager = getLoaderManager(); loaderManager.restartLoader(PeopleIveMetQuery.TOKEN, null, this); loaderManager.restartLoader(PeopleIveMetSubQuery.TOKEN, null, this); // Set up the API client GoogleApiClient.Builder builder = new GoogleApiClient.Builder(activity, this, this) .addApi(Plus.API) .addScope(Plus.SCOPE_PLUS_LOGIN); if (AccountUtils.hasActiveAccount(activity)) { builder.setAccountName(AccountUtils.getActiveAccountName(activity)); } mApiClient = builder.build(); } } @Override public void onStart() { super.onStart(); mApiClient.connect(); } @Override public void onStop() { super.onStop(); if (mApiClient.isConnected()) { mApiClient.disconnect(); } } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { switch (id) { case PeopleIveMetQuery.TOKEN: { return new CursorLoader(getActivity(), ScheduleContract.PeopleIveMet.CONTENT_URI, PeopleIveMetQuery.PROJECTION, null, null, ScheduleContract.PeopleIveMet.DEFAULT_SORT); } case PeopleIveMetSubQuery.TOKEN: { return new CursorLoader(getActivity(), ScheduleContract.PeopleIveMet.CONTENT_URI, PeopleIveMetSubQuery.PROJECTION, PeopleIveMetSubQuery.SELECTION, null, null); } } return null; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { switch (loader.getId()) { case PeopleIveMetQuery.TOKEN: { if (0 == cursor.getCount()) { EmptyAdapter adapter = new EmptyAdapter(getActivity()); mCollectionView.setCollectionAdapter(adapter); mCollectionView.updateInventory(adapter.getInventory()); } else { PeopleIveMetAdapter adapter = new PeopleIveMetAdapter(getActivity(), cursor); mCollectionView.setCollectionAdapter(adapter); mCollectionView.updateInventory(adapter.getInventory()); } break; } case PeopleIveMetSubQuery.TOKEN: { if (!mCallingApi) { mPlusIds.clear(); while (cursor.moveToNext()) { mPlusIds.add(cursor.getString(PeopleIveMetSubQuery.PERSON_ID)); } fillInMissingData(); } break; } } } @Override public void onLoaderReset(Loader<Cursor> loader) { switch (loader.getId()) { case PeopleIveMetQuery.TOKEN: { mCollectionView.updateInventory(new CollectionView.Inventory()); break; } case PeopleIveMetSubQuery.TOKEN: { mPlusIds.clear(); break; } } } @Override public void onConnected(Bundle bundle) { LOGD(TAG, "onConnected"); fillInMissingData(); hideApiError(); } @Override public void onConnectionSuspended(int i) { // We just wait until the API is available } @Override public void onConnectionFailed(ConnectionResult result) { if (result.hasResolution()) { try { Activity activity = getActivity(); if (null != activity) { result.startResolutionForResult(activity, PeopleIveMetActivity.REQUEST_RESOLUTION_FOR_RESULT); } } catch (IntentSender.SendIntentException e) { e.printStackTrace(); } } else { showApiError(); } } private void fillInMissingData() { if (mPlusIds.isEmpty()) { return; } if (!mApiClient.isConnected()) { return; } LOGD(TAG, "fillInMissingData (this should appear only once)"); mCallingApi = true; Plus.PeopleApi.load(mApiClient, mPlusIds).setResultCallback(this); } void showApiError() { mApiError.setVisibility(View.VISIBLE); } void hideApiError() { mApiError.setVisibility(View.GONE); } void retry() { mApiClient.connect(); } @Override public void onResult(final People.LoadPeopleResult loadPeopleResult) { if (!loadPeopleResult.getStatus().isSuccess()) { showApiError(); return; } new AsyncTask<Void, Void, Integer>() { @Override protected Integer doInBackground(Void... params) { PersonBuffer personBuffer = loadPeopleResult.getPersonBuffer(); ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); for (int i = 0, max = personBuffer.getCount(); i < max; ++i) { Person person = personBuffer.get(i); ContentValues values = new ContentValues(); values.put(ScheduleContract.PeopleIveMet.PERSON_NAME, person.getDisplayName()); values.put(ScheduleContract.PeopleIveMet.PERSON_IMAGE_URL, person.getImage().getUrl()); operations.add(ContentProviderOperation.newUpdate( ScheduleContract.PeopleIveMet.buildPersonUri(mPlusIds.get(i))) .withValues(values) .build()); } ContentResolver resolver = getActivity().getContentResolver(); try { resolver.applyBatch(ScheduleContract.CONTENT_AUTHORITY, operations); return operations.size(); } catch (RemoteException e) { e.printStackTrace(); } catch (OperationApplicationException e) { e.printStackTrace(); } return 0; } @Override protected void onPostExecute(Integer result) { mCallingApi = false; } }.execute(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.retry: { retry(); break; } } } private interface PeopleIveMetQuery { final int TOKEN = 0x1; final String[] PROJECTION = { ScheduleContract.PeopleIveMet._ID, ScheduleContract.PeopleIveMet.PERSON_ID, ScheduleContract.PeopleIveMet.PERSON_NAME, ScheduleContract.PeopleIveMet.PERSON_IMAGE_URL, ScheduleContract.PeopleIveMet.PERSON_NOTE, ScheduleContract.PeopleIveMet.PERSON_TIMESTAMP, }; final int PERSON_ID = 1; final int PERSON_NAME = 2; final int PERSON_IMAGE_URL = 3; final int PERSON_NOTE = 4; final int PERSON_TIMESTAMP = 5; } private interface PeopleIveMetSubQuery { final int TOKEN = 0x2; final String[] PROJECTION = { ScheduleContract.PeopleIveMet.PERSON_ID, }; final int PERSON_ID = 0; final String SELECTION = ScheduleContract.PeopleIveMet.PERSON_NAME + " IS NULL"; } private static class EmptyAdapter implements CollectionViewCallbacks { private final Context mContext; public EmptyAdapter(Context context) { mContext = context; } public CollectionView.Inventory getInventory() { CollectionView.Inventory inventory = new CollectionView.Inventory(); inventory.addGroup(new CollectionView.InventoryGroup(PeopleIveMetQuery.TOKEN) .setDisplayCols(1) .setItemCount(1) .setDataIndexStart(0) .setShowHeader(true) .setHeaderLabel(mContext.getString(R.string.title_people_ive_met))); return inventory; } @Override public View newCollectionHeaderView(Context context, ViewGroup parent) { return LayoutInflater.from(context).inflate(R.layout.header_people_ive_met, parent, false); } @Override public void bindCollectionHeaderView(Context context, View view, int groupId, String headerLabel) { } @Override public View newCollectionItemView(Context context, int groupId, ViewGroup parent) { return LayoutInflater.from(context).inflate(R.layout.list_item_people_ive_met_empty, parent, false); } @Override public void bindCollectionItemView(Context context, View view, int groupId, int indexInGroup, int dataIndex, Object tag) { } } private static class PeopleIveMetAdapter extends CursorAdapter implements CollectionViewCallbacks, View.OnClickListener, PopupMenu.OnMenuItemClickListener { private static final int OFFSET = 2; private final ImageLoader mImageLoader; private final FragmentManager mFragmentManager; private final Cursor mCursor; private final Context mContext; private String mCurrentMenuPersonId; private String mCurrentMenuPersonName; private String mCurrentMenuPersonNote; public PeopleIveMetAdapter(Activity activity, Cursor c) { super(activity, c, 0); mCursor = c; mContext = activity; mImageLoader = new ImageLoader(activity, R.drawable.person_image_empty); mFragmentManager = activity.getFragmentManager(); } private static String getPlusUrl(String plusId) { return "https://plus.google.com/" + plusId; } public CollectionView.Inventory getInventory() { CollectionView.Inventory inventory = new CollectionView.Inventory(); inventory.addGroup(new CollectionView.InventoryGroup(PeopleIveMetQuery.TOKEN) .setDisplayCols(1) .setItemCount(mCursor.getCount()) .setDataIndexStart(0) .setShowHeader(true) .setHeaderLabel(mContext.getString(R.string.title_people_ive_met))); return inventory; } @Override public View newCollectionHeaderView(Context context, ViewGroup parent) { return LayoutInflater.from(context).inflate(R.layout.header_people_ive_met, parent, false); } @Override public void bindCollectionHeaderView(Context context, View view, int groupId, String headerLabel) { } @Override public View newCollectionItemView(Context context, int groupId, ViewGroup parent) { return newView(context, null, parent); } @Override public void bindCollectionItemView(Context context, View view, int groupId, int indexInGroup, int dataIndex, Object tag) { setCursorPosition(indexInGroup); bindView(view, context, mCursor); } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = LayoutInflater.from(context).inflate(R.layout.list_item_people_ive_met, parent, false); assert view != null; ViewHolder holder = new ViewHolder(); holder.image = (ImageView) view.findViewById(R.id.image); holder.name = (TextView) view.findViewById(R.id.name); holder.timestamp = (TextView) view.findViewById(R.id.timestamp); holder.note = (TextView) view.findViewById(R.id.note); holder.actions = (ImageButton) view.findViewById(R.id.actions); view.setTag(holder); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { String personId = cursor.getString(PeopleIveMetQuery.PERSON_ID); String personName = cursor.getString(PeopleIveMetQuery.PERSON_NAME); String note = cursor.getString(PeopleIveMetQuery.PERSON_NOTE); ViewHolder holder = (ViewHolder) view.getTag(); // Click listeners view.setOnClickListener(this); view.setTag(R.id.tag_person_id, personId); holder.actions.setOnClickListener(this); holder.actions.setTag(R.id.tag_person_id, personId); holder.actions.setTag(R.id.tag_person_name, personName); holder.actions.setTag(R.id.tag_person_note, note); // Display the image and labels if (!cursor.isNull(PeopleIveMetQuery.PERSON_IMAGE_URL)) { mImageLoader.loadImage(cursor.getString(PeopleIveMetQuery.PERSON_IMAGE_URL), holder.image); } if (TextUtils.isEmpty(personName)) { holder.name.setText(getPlusUrl(personId)); } else { holder.name.setText(personName); } holder.timestamp.setText(DateUtils.getRelativeDateTimeString(mContext, cursor.getLong(PeopleIveMetQuery.PERSON_TIMESTAMP), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0)); // note if (TextUtils.isEmpty(note)) { holder.note.setVisibility(View.GONE); } else { holder.note.setVisibility(View.VISIBLE); holder.note.setText(note); } } private void setCursorPosition(int position) { if (!mCursor.moveToPosition(position)) { throw new IllegalStateException("couldn't move cursor to position " + position); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.container: { String personId = (String) v.getTag(R.id.tag_person_id); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(getPlusUrl(personId))); UIUtils.preferPackageForIntent(mContext, intent, UIUtils.GOOGLE_PLUS_PACKAGE_NAME); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); mContext.startActivity(intent); break; } case R.id.actions: { mCurrentMenuPersonId = (String) v.getTag(R.id.tag_person_id); mCurrentMenuPersonName = (String) v.getTag(R.id.tag_person_name); mCurrentMenuPersonNote = (String) v.getTag(R.id.tag_person_note); PopupMenu popup = new PopupMenu(mContext, v); popup.getMenuInflater().inflate(R.menu.people_ive_met, popup.getMenu()); popup.setOnMenuItemClickListener(this); popup.show(); break; } } } @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.note: { EditNoteFragment.newInstance(mCurrentMenuPersonId, mCurrentMenuPersonName, mCurrentMenuPersonNote) .show(mFragmentManager, EditNoteFragment.TAG); return true; } case R.id.delete: { new AlertDialog.Builder(mContext) .setMessage(R.string.people_ive_met_delete_confirmation) .setPositiveButton(R.string.people_ive_met_delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { new DeletePersonTask(mContext, mCurrentMenuPersonId) .execute(); } } ) .setNegativeButton(android.R.string.cancel, null) .show(); return true; } } return false; } private static class ViewHolder { ImageView image; TextView name; TextView timestamp; TextView note; ImageButton actions; } } private static class DeletePersonTask extends AsyncTask<Void, Void, Boolean> { private final Context mContext; private final String mPersonId; public DeletePersonTask(Context context, String personId) { mContext = context; mPersonId = personId; } @Override protected Boolean doInBackground(Void... params) { return 0 < mContext.getContentResolver() .delete(ScheduleContract.PeopleIveMet.buildPersonUri(mPersonId), null, null); } } private static class UpdatePersonNoteTask extends AsyncTask<Void, Void, Boolean> { private final Context mContext; private final String mPersonId; private final String mNote; public UpdatePersonNoteTask(Context context, String personId, String note) { mContext = context; mPersonId = personId; mNote = note; } @Override protected Boolean doInBackground(Void... params) { ContentValues values = new ContentValues(); values.put(ScheduleContract.PeopleIveMet.PERSON_NOTE, mNote); return 0 < mContext.getContentResolver() .update(ScheduleContract.PeopleIveMet.buildPersonUri(mPersonId), values, null, null); } } public static class EditNoteFragment extends DialogFragment implements View.OnClickListener { public static final String TAG = "com.google.samples.apps.iosched.ui.PeopleIveMetFragment.EditNoteFragment"; private static final String ARG_PERSON_ID = "person_id"; private static final String ARG_PERSON_NAME = "person_name"; private static final String ARG_DEFAULT_CONTENT = "default_content"; private EditText mEditNote; public EditNoteFragment() { } public static EditNoteFragment newInstance(String personId, String personName, String defaultContent) { EditNoteFragment fragment = new EditNoteFragment(); Bundle args = new Bundle(); args.putString(ARG_PERSON_ID, personId); args.putString(ARG_PERSON_NAME, personName); args.putString(ARG_DEFAULT_CONTENT, defaultContent); fragment.setArguments(args); return fragment; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Set title Bundle args = getArguments(); String personName = args.getString(ARG_PERSON_NAME); if (TextUtils.isEmpty(personName)) { getDialog().setTitle(getString(R.string.people_ive_met_edit_note_title_with_name, personName)); } else { getDialog().setTitle(getString(R.string.people_ive_met_edit_note_title)); } // Inflate the dialog content return inflater.inflate(R.layout.fragment_edit_note, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { mEditNote = (EditText) view.findViewById(R.id.note); Bundle args = getArguments(); mEditNote.setText(args.getString(ARG_DEFAULT_CONTENT)); view.findViewById(R.id.ok).setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.ok: { dismiss(); break; } } } @Override public void onDismiss(DialogInterface dialog) { Bundle args = getArguments(); String personId = args.getString(ARG_PERSON_ID); new UpdatePersonNoteTask(getActivity(), personId, mEditNote.getText().toString()) .execute(); super.onDismiss(dialog); } } }