/*
* Copyright (c) 2013, Psiphon Inc.
* All rights reserved.
*
* 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 ca.psiphon.ploggy;
import java.util.List;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.squareup.otto.Subscribe;
/**
* User interface which displays a list of friends.
* This class subscribes to friend and status events to update displayed data
* while in the foreground.
*/
public class FragmentFriendList extends ListFragment {
private static final String LOG_TAG = "Friend List";
private boolean mIsResumed = false;
private FriendAdapter mFriendAdapter;
Utils.FixedDelayExecutor mRefreshUIExecutor;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
try {
mFriendAdapter = new FriendAdapter(getActivity());
} catch (Utils.ApplicationError e) {
Log.addEntry(LOG_TAG, "failed to initialize friend adapter");
}
// Refresh the message list every 5 seconds. This updates "time ago" displays.
// TODO: event driven redrawing?
mRefreshUIExecutor = new Utils.FixedDelayExecutor(new Runnable() {@Override public void run() {updateFriends();}}, 5000);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mFriendAdapter != null) {
setListAdapter(mFriendAdapter);
}
registerForContextMenu(getListView());
Events.register(this);
}
@Override
public void onResume() {
super.onResume();
mIsResumed = true;
mRefreshUIExecutor.start();
Events.post(new Events.DisplayedFriends());
}
@Override
public void onPause() {
super.onPause();
mIsResumed = false;
mRefreshUIExecutor.stop();
}
@Override
public void onDestroyView() {
Events.unregister(this);
super.onDestroyView();
}
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
Data.Friend friend = (Data.Friend)listView.getItemAtPosition(position);
Intent intent = new Intent(getActivity(), ActivityFriendStatusDetails.class);
Bundle bundle = new Bundle();
bundle.putString(ActivityFriendStatusDetails.FRIEND_ID_BUNDLE_KEY, friend.mId);
intent.putExtras(bundle);
startActivity(intent);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
if (view.equals(getListView())) {
getActivity().getMenuInflater().inflate(R.menu.friend_list_context, menu);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
if (item.getItemId() == R.id.action_friend_list_delete_friend) {
final Data.Friend finalFriend = (Data.Friend)getListView().getItemAtPosition(info.position);
new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.label_delete_friend_title))
.setMessage(getString(R.string.label_delete_friend_message, finalFriend.mPublicIdentity.mNickname))
.setPositiveButton(getString(R.string.label_delete_friend_positive),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
Data.getInstance().removeFriend(finalFriend.mId);
} catch (Data.DataNotFoundError e) {
// Ignore
} catch (Utils.ApplicationError e) {
Log.addEntry(LOG_TAG, "failed to delete friend: " + finalFriend.mPublicIdentity.mNickname);
}
}
})
.setNegativeButton(getString(R.string.label_delete_friend_negative), null)
.show();
return true;
}
return super.onContextItemSelected(item);
}
@Subscribe
public void onAddedFriend(Events.AddedFriend addedFriend) {
updateFriends();
}
@Subscribe
public void onUpdatedFriend(Events.UpdatedFriend updatedFriend) {
updateFriends();
}
@Subscribe
public void onUpdatedFriendStatus(Events.UpdatedFriendStatus updatedFriendStatus) {
updateFriends();
}
@Subscribe
public void onDeletedFriend(Events.RemovedFriend removedFriend) {
updateFriends();
}
@Subscribe
public void onUpdatedNewMessages(Events.UpdatedNewMessages updatedNewMessages) {
if (mIsResumed) {
Events.post(new Events.DisplayedFriends());
}
}
private void updateFriends() {
try {
mFriendAdapter.updateFriends();
} catch (Utils.ApplicationError e) {
Log.addEntry(LOG_TAG, "failed to update friend list");
}
}
private static class FriendAdapter extends BaseAdapter {
private final Context mContext;
private List<Data.Friend> mFriends;
public FriendAdapter(Context context) throws Utils.ApplicationError {
mContext = context;
mFriends = Data.getInstance().getFriends();
}
public void updateFriends() throws Utils.ApplicationError {
mFriends = Data.getInstance().getFriends();
notifyDataSetChanged();
}
@Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.friend_list_row, null);
}
Data.Friend friend = mFriends.get(position);
if (friend != null) {
ImageView avatarImage = (ImageView)view.findViewById(R.id.friend_list_avatar_image);
TextView nicknameText = (TextView)view.findViewById(R.id.friend_list_nickname_text);
TextView lastTimestampText = (TextView)view.findViewById(R.id.friend_list_last_timestamp_text);
TextView messageTimestampText = (TextView)view.findViewById(R.id.friend_list_message_timestamp_text);
TextView messageContentText = (TextView)view.findViewById(R.id.friend_list_message_content_text);
TextView locationTimestampText = (TextView)view.findViewById(R.id.friend_list_location_timestamp_text);
TextView locationStreetAddressText = (TextView)view.findViewById(R.id.friend_list_location_street_address_text);
TextView locationDistanceText = (TextView)view.findViewById(R.id.friend_list_location_distance_text);
// Not hiding missing fields
lastTimestampText.setText("");
messageTimestampText.setText("");
messageContentText.setText("");
locationTimestampText.setText("");
locationStreetAddressText.setText("");
locationDistanceText.setText("");
Robohash.setRobohashImage(mContext, avatarImage, true, friend.mPublicIdentity);
nicknameText.setText(friend.mPublicIdentity.mNickname);
try {
Data data = Data.getInstance();
Data.Location selfLocation = null;
try {
selfLocation = data.getCurrentSelfLocation();
} catch (Data.DataNotFoundError e) {
// Won't be able to compute distance
}
Data.Status friendStatus = data.getFriendStatus(friend.mId);
// Display most recent successful communication timestamp
String lastTimestamp = "";
if (friend.mLastReceivedStatusTimestamp != null &&
(friend.mLastSentStatusTimestamp == null ||
friend.mLastReceivedStatusTimestamp.after(friend.mLastSentStatusTimestamp))) {
lastTimestamp = Utils.DateFormatter.formatRelativeDatetime(mContext, friend.mLastReceivedStatusTimestamp, true);
} else if (friend.mLastSentStatusTimestamp != null) {
lastTimestamp = Utils.DateFormatter.formatRelativeDatetime(mContext, friend.mLastSentStatusTimestamp, true);
}
lastTimestampText.setText(lastTimestamp);
if (lastTimestamp.length() > 0) {
// On touch, show log entries
lastTimestampText.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
mContext.startActivity(new Intent(mContext, ActivityLogEntries.class));
}
});
}
if (friendStatus.mMessages.size() > 0) {
Data.Message message = friendStatus.mMessages.get(0);
messageContentText.setText(message.mContent);
messageTimestampText.setText(Utils.DateFormatter.formatRelativeDatetime(mContext, message.mTimestamp, true));
}
if (friendStatus.mLocation.mTimestamp != null) {
locationTimestampText.setText(Utils.DateFormatter.formatRelativeDatetime(mContext, friendStatus.mLocation.mTimestamp, true));
if (friendStatus.mLocation.mStreetAddress.length() > 0) {
locationStreetAddressText.setText(friendStatus.mLocation.mStreetAddress);
} else {
locationStreetAddressText.setText(R.string.prompt_no_street_address_reported);
}
if (selfLocation != null && selfLocation.mTimestamp != null) {
int distance = Utils.calculateLocationDistanceInMeters(
selfLocation.mLatitude,
selfLocation.mLongitude,
friendStatus.mLocation.mLatitude,
friendStatus.mLocation.mLongitude);
locationDistanceText.setText(Utils.formatDistance(mContext, distance));
} else {
locationDistanceText.setText(R.string.prompt_unknown_distance);
}
}
} catch (Data.DataNotFoundError e) {
messageTimestampText.setText(R.string.prompt_no_status_updates_received);
} catch (Utils.ApplicationError e) {
Log.addEntry(LOG_TAG, "failed to display friend");
}
}
return view;
}
@Override
public int getCount() {
return mFriends.size();
}
@Override
public Object getItem(int position) {
return mFriends.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
}
}