/**
* Copyright 2010 Mark Wyszomierski
*/
package com.joelapenna.foursquared;
import com.joelapenna.foursquare.Foursquare;
import com.joelapenna.foursquare.types.Badge;
import com.joelapenna.foursquare.types.CheckinResult;
import com.joelapenna.foursquare.types.Group;
import com.joelapenna.foursquare.types.Mayor;
import com.joelapenna.foursquare.types.Score;
import com.joelapenna.foursquare.types.Special;
import com.joelapenna.foursquare.types.User;
import com.joelapenna.foursquare.types.Venue;
import com.joelapenna.foursquared.util.Base64Coder;
import com.joelapenna.foursquared.util.RemoteResourceManager;
import com.joelapenna.foursquared.widget.BadgeWithIconListAdapter;
import com.joelapenna.foursquared.widget.ScoreListAdapter;
import com.joelapenna.foursquared.widget.SeparatedListAdapter;
import com.joelapenna.foursquared.widget.SpecialListAdapter;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import java.io.IOException;
import java.util.Observable;
import java.util.Observer;
/**
* Renders the result of a checkin using a CheckinResult object. This is called
* from CheckinExecuteActivity. It would be nicer to put this in another activity,
* but right now the CheckinResult is quite large and would require a good amount
* of work to add serializers for all its inner classes. This wouldn't be a huge
* problem, but maintaining it as the classes evolve could more trouble than it's
* worth.
*
* The only way the user can dismiss this dialog is by hitting the 'back' key.
* CheckingExecuteActivity depends on this so it knows when to finish() itself.
*
* @date March 3, 2010.
* @author Mark Wyszomierski (markww@gmail.com), foursquare.
*
*/
public class CheckinResultDialog extends Dialog
{
private static final String TAG = "CheckinResultDialog";
private static final boolean DEBUG = FoursquaredSettings.DEBUG;
private CheckinResult mCheckinResult;
private Handler mHandler;
private RemoteResourceManagerObserver mObserverMayorPhoto;
private Foursquared mApplication;
private String mExtrasDecoded;
private WebViewDialog mDlgWebViewExtras;
public CheckinResultDialog(Context context, CheckinResult result, Foursquared application) {
super(context, R.style.ThemeCustomDlgBase_ThemeCustomDlg);
mCheckinResult = result;
mApplication = application;
mHandler = new Handler();
mObserverMayorPhoto = null;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.checkin_result_dialog);
setTitle(getContext().getResources().getString(R.string.checkin_title_result));
TextView tvMessage = (TextView)findViewById(R.id.textViewCheckinMessage);
if (mCheckinResult != null) {
tvMessage.setText(mCheckinResult.getMessage());
SeparatedListAdapter adapter = new SeparatedListAdapter(getContext());
// Add any badges the user unlocked as a result of this checkin.
addBadges(mCheckinResult.getBadges(), adapter, mApplication.getRemoteResourceManager());
// Add whatever points they got as a result of this checkin.
addScores(mCheckinResult.getScoring(), adapter, mApplication.getRemoteResourceManager());
// Add any specials that are nearby.
addSpecials(mCheckinResult.getSpecials(), adapter);
// Add a button below the mayor section which will launch a new webview if
// we have additional content from the server. This is base64 encoded and
// is supposed to be just dumped into a webview.
addExtras(mCheckinResult.getMarkup());
// List items construction complete.
ListView listview = (ListView)findViewById(R.id.listViewCheckinBadgesAndScores);
listview.setAdapter(adapter);
listview.setOnItemClickListener(mOnItemClickListener);
// Show mayor info if any.
addMayor(mCheckinResult.getMayor(), mApplication.getRemoteResourceManager());
} else {
// This shouldn't be possible but we've gotten a few crash reports showing that
// mCheckinResult is null on entry of this method.
Log.e(TAG, "Checkin result object was null on dialog creation.");
tvMessage.setText("Checked-in!");
}
}
@Override
protected void onStop() {
super.onStop();
if (mDlgWebViewExtras != null && mDlgWebViewExtras.isShowing()) {
mDlgWebViewExtras.dismiss();
}
if (mObserverMayorPhoto != null) {
mApplication.getRemoteResourceManager().deleteObserver(mObserverMayorPhoto);
}
}
private void addBadges(Group<Badge> badges, SeparatedListAdapter adapterMain, RemoteResourceManager rrm) {
if (badges == null || badges.size() < 1) {
return;
}
BadgeWithIconListAdapter adapter = new BadgeWithIconListAdapter(
getContext(), rrm, R.layout.badge_list_item);
adapter.setGroup(badges);
adapterMain.addSection(getContext().getResources().getString(R.string.checkin_result_dialog_badges),
adapter);
}
private void addScores(Group<Score> scores,
SeparatedListAdapter adapterMain,
RemoteResourceManager rrm) {
if (scores == null || scores.size() < 1) {
return;
}
// We make our own local score group because we'll inject the total as
// a new dummy score element.
Group<Score> scoresWithTotal = new Group<Score>();
// Total up the scoring.
int total = 0;
for (Score score : scores) {
total += Integer.parseInt(score.getPoints());
scoresWithTotal.add(score);
}
// Add a dummy score element to the group which is just the total.
Score scoreTotal = new Score();
scoreTotal.setIcon("");
scoreTotal.setMessage(getContext().getResources().getString(
R.string.checkin_result_dialog_score_total));
scoreTotal.setPoints(String.valueOf(total));
scoresWithTotal.add(scoreTotal);
// Give it all to the adapter now.
ScoreListAdapter adapter = new ScoreListAdapter(getContext(), rrm);
adapter.setGroup(scoresWithTotal);
adapterMain.addSection(getContext().getResources().getString(R.string.checkin_score), adapter);
}
private void addMayor(Mayor mayor, RemoteResourceManager rrm) {
LinearLayout llMayor = (LinearLayout)findViewById(R.id.llCheckinMayorInfo);
if (mayor == null) {
llMayor.setVisibility(View.GONE);
return;
} else {
llMayor.setVisibility(View.VISIBLE);
}
// Set the mayor message.
TextView tvMayorMessage = (TextView)findViewById(R.id.textViewCheckinMayorMessage);
tvMayorMessage.setText(mayor.getMessage());
// A few cases here for the image to display.
ImageView ivMayor = (ImageView)findViewById(R.id.imageViewCheckinMayor);
if (mCheckinResult.getMayor().getUser() == null) {
// I am still the mayor.
// Just show the crown icon.
ivMayor.setImageDrawable(getContext().getResources().getDrawable(R.drawable.crown));
}
else if (mCheckinResult.getMayor().getType().equals("nochange")) {
// Someone else is mayor.
// Show that user's photo from the network. If not already on disk,
// we need to start a fetch for it.
Uri photoUri = populateMayorImageFromNetwork();
if (photoUri != null) {
mApplication.getRemoteResourceManager().request(photoUri);
mObserverMayorPhoto = new RemoteResourceManagerObserver();
rrm.addObserver(mObserverMayorPhoto);
}
addClickHandlerForMayorImage(ivMayor, mayor.getUser().getId());
}
else if (mCheckinResult.getMayor().getType().equals("new")) {
// I just became the new mayor as a result of this checkin.
// Just show the crown icon.
ivMayor.setImageDrawable(getContext().getResources().getDrawable(R.drawable.crown));
}
else if (mCheckinResult.getMayor().getType().equals("stolen")) {
// I stole mayorship from someone else as a result of this checkin.
// Just show the crown icon.
ivMayor.setImageDrawable(getContext().getResources().getDrawable(R.drawable.crown));
}
}
private void addSpecials(Group<Special> specials,
SeparatedListAdapter adapterMain) {
if (specials == null || specials.size() < 1) {
return;
}
// For now, get rid of specials not tied to the current venue. If the special is
// tied to this venue, then there would be no <venue> block associated with the
// special. If there is a <venue> block associated with the special, it means it
// belongs to another venue and we won't show it.
Group<Special> localSpecials = new Group<Special>();
for (Special it : specials) {
if (it.getVenue() == null) {
localSpecials.add(it);
}
}
if (localSpecials.size() < 1) {
return;
}
SpecialListAdapter adapter = new SpecialListAdapter(getContext());
adapter.setGroup(localSpecials);
adapterMain.addSection(
getContext().getResources().getString(R.string.checkin_specials), adapter);
}
private void addExtras(String extras) {
LinearLayout llExtras = (LinearLayout)findViewById(R.id.llCheckinExtras);
if (TextUtils.isEmpty(extras)) {
llExtras.setVisibility(View.GONE);
return;
} else {
llExtras.setVisibility(View.VISIBLE);
}
// The server sent us additional content, it is base64 encoded, so decode it now.
mExtrasDecoded = Base64Coder.decodeString(extras);
// TODO: Replace with generic extras method.
// Now when the user clicks this 'button' pop up yet another dialog dedicated
// to showing just the webview and the decoded content. This is not ideal but
// having problems putting a webview directly inline with the rest of the
// checkin content, we can improve this later.
llExtras.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDlgWebViewExtras = new WebViewDialog(getContext(), "SXSW Stats", mExtrasDecoded);
mDlgWebViewExtras.show();
}
});
}
private void addClickHandlerForMayorImage(View view, final String userId) {
// Show a user detail activity when the user clicks on the mayor's image.
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(v.getContext(), UserDetailsActivity.class);
intent.putExtra(UserDetailsActivity.EXTRA_USER_ID, userId);
intent.putExtra(UserDetailsActivity.EXTRA_SHOW_ADD_FRIEND_OPTIONS, true);
v.getContext().startActivity(intent);
}
});
}
/**
* If we have to download the user's photo from the net (wasn't already in cache)
* will return the uri to launch.
*/
private Uri populateMayorImageFromNetwork() {
User user = mCheckinResult.getMayor().getUser();
ImageView ivMayor = (ImageView)findViewById(R.id.imageViewCheckinMayor);
if (user != null) {
Uri photoUri = Uri.parse(user.getPhoto());
try {
Bitmap bitmap = BitmapFactory.decodeStream(
mApplication.getRemoteResourceManager().getInputStream(photoUri));
ivMayor.setImageBitmap(bitmap);
return null;
} catch (IOException e) {
// User's image wasn't already in the cache, have to start a request for it.
if (Foursquare.MALE.equals(user.getGender())) {
ivMayor.setImageResource(R.drawable.blank_boy);
} else {
ivMayor.setImageResource(R.drawable.blank_girl);
}
return photoUri;
}
}
return null;
}
/**
* Called if the remote resource manager downloads the mayor's photo.
* If the photo is already on disk, this observer will never be used.
*/
private class RemoteResourceManagerObserver implements Observer {
@Override
public void update(Observable observable, Object data) {
if (DEBUG) Log.d(TAG, "Fetcher got: " + data);
mHandler.post(new Runnable() {
@Override
public void run() {
populateMayorImageFromNetwork();
}
});
}
}
private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
Object obj = adapter.getItemAtPosition(position);
if (obj != null) {
if (obj instanceof Special) {
// When the user clicks on a special, if the venue is different than
// the venue the user checked in at (already being viewed) then show
// a new venue activity for that special.
Venue venue = ((Special)obj).getVenue();
if (venue != null) {
Intent intent = new Intent(getContext(), VenueActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(VenueActivity.INTENT_EXTRA_VENUE_PARTIAL, venue);
getContext().startActivity(intent);
}
}
}
}
};
}