package what.whatandroid.profile;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.text.format.DateUtils;
import android.view.*;
import android.widget.*;
import api.cli.Utils;
import api.index.Index;
import api.soup.MySoup;
import api.user.Profile;
import api.user.User;
import api.user.UserProfile;
import api.user.recent.UserRecents;
import what.whatandroid.R;
import what.whatandroid.WhatApplication;
import what.whatandroid.callbacks.LoadingListener;
import what.whatandroid.callbacks.OnLoggedInCallback;
import what.whatandroid.callbacks.SetTitleCallback;
import what.whatandroid.forums.thread.ReplyDialogFragment;
import what.whatandroid.settings.SettingsActivity;
import java.util.Date;
/**
* Fragment to display a user's profile
*/
public class ProfileFragment extends Fragment implements OnLoggedInCallback,
LoaderManager.LoaderCallbacks<UserProfile>, ReplyDialogFragment.ReplyDialogListener {
public static final String DEFER_LOADING = "what.whatandroid.DEFER_LOADING";
/**
* The user's profile information
*/
private UserProfile userProfile;
/**
* The user id we want to view, passed earlier as a param since we defer loading until onCreate
*/
private int userID;
private boolean deferLoad;
/**
* Callbacks to the activity so we can go set the title
*/
private SetTitleCallback setTitle;
private LoadingListener<Index> indexLoadingListener;
/**
* Various content views displaying the user's information
*/
private ImageView avatar;
private ProgressBar spinner;
private View artContainer;
/**
* The user's stats being shown
*/
private TextView username, userClass, joined, invited, ratio, paranoia;
/**
* Text views saying what the various numbers in the profile mean, so we can hide those that are hidden
* by the user's paranoia
*/
private TextView invitedText, ratioText, paranoiaText;
/**
* View pagers & adapters for displaying the lists of recent snatches and uploads & headers for the views
* headers are needed so we can hide the views if hidden by paranoia
*/
private RecentTorrentPagerAdapter snatchesAdapter, uploadsAdapter;
private View snatchesContainer, uploadsContainer, donor, warned, banned;
/**
* Draft of a message we're writing to the user
*/
private String messageDraft = "", messageSubject = "";
/**
* Send message menu item, so we can hide it if we're viewing our own profile
*/
private MenuItem sendMessage;
/**
* Text views to display user ranks
*/
private TextView dataUploadedText, dataUploaded, dataDownloadedText, dataDownloaded, torrentsUploadedText, torrentsUploaded, requestsFilledText, requestsFilled, bountySpentText, bountySpent, postsMadeText, postsMade, artistsAddedText, artistsAdded, overallText, overall;
/**
* Text views to display user rank values
*/
private TextView dataUploadedValue,dataDownloadedValue, torrentsUploadedValue, requestsFilledValue, postsMadeValue;
/**
* Button to show/collapse the sections
*/
private RelativeLayout toggleRanks;
private RelativeLayout toggleStats;
private RelativeLayout toggleSnatches;
private RelativeLayout toggleUploads;
private ImageView toggleStatsBtn;
private ImageView toggleRanksBtn;
private ImageView toggleSnatchesBtn;
private ImageView toggleUploadsBtn;
/**
* Section view layouts
*/
private RelativeLayout ranksView;
private RelativeLayout statsView;
private WrappingViewPager snatchesView;
private WrappingViewPager uploadsView;
/**
* Use this factory method to create a new instance of the fragment displaying the
* desired user's profile
*
* @param id The user id to display the profile of
* @param deferLoad True if the fragment should wait to load the profile until the user id is updated
*/
public static ProfileFragment newInstance(int id, boolean deferLoad) {
ProfileFragment fragment = new ProfileFragment();
Bundle args = new Bundle();
args.putInt(ProfileActivity.USER_ID, id);
args.putBoolean(ProfileFragment.DEFER_LOADING, deferLoad);
fragment.setArguments(args);
return fragment;
}
public ProfileFragment() {
// Required empty public constructor
}
public void setUserID(int id) {
if (deferLoad) {
userID = id;
getArguments().putInt(ProfileActivity.USER_ID, userID);
//We now have the right id so we don't need to defer loading anymore
getArguments().putBoolean(ProfileFragment.DEFER_LOADING, false);
Bundle args = new Bundle();
args.putInt(ProfileActivity.USER_ID, userID);
getLoaderManager().initLoader(0, args, this);
}
}
/**
* Get the user id the fragment is currently viewing
*
* @return viewed user's id
*/
public int getUserID() {
return userID;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
setTitle = (SetTitleCallback) activity;
indexLoadingListener = (LoadingListener<Index>) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement ViewTorrent & SetTitle Callbacks");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userID = getArguments().getInt(ProfileActivity.USER_ID);
deferLoad = getArguments().getBoolean(ProfileFragment.DEFER_LOADING);
setHasOptionsMenu(true);
if (savedInstanceState != null) {
messageDraft = savedInstanceState.getString(ReplyDialogFragment.DRAFT);
messageSubject = savedInstanceState.getString(ReplyDialogFragment.SUBJECT);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_profile, container, false);
avatar = (ImageView) view.findViewById(R.id.avatar);
spinner = (ProgressBar) view.findViewById(R.id.loading_indicator);
artContainer = view.findViewById(R.id.art_container);
username = (TextView) view.findViewById(R.id.username);
userClass = (TextView) view.findViewById(R.id.user_class);
joined = (TextView) view.findViewById(R.id.joined);
invited = (TextView) view.findViewById(R.id.invited);
invitedText = (TextView) view.findViewById(R.id.invited_text);
ratio = (TextView) view.findViewById(R.id.ratio);
ratioText = (TextView) view.findViewById(R.id.ratio_text);
paranoia = (TextView) view.findViewById(R.id.paranoia);
paranoiaText = (TextView) view.findViewById(R.id.paranoia_text);
//Hide the paranoia text until we figure out what the user's paranoia settings are
paranoiaText.setVisibility(View.GONE);
ViewPager recentSnatches = (ViewPager) view.findViewById(R.id.recent_snatches);
snatchesContainer = view.findViewById(R.id.snatches_container);
ViewPager recentUploads = (ViewPager) view.findViewById(R.id.recent_uploads);
uploadsContainer = view.findViewById(R.id.uploads_container);
donor = view.findViewById(R.id.donor);
warned = view.findViewById(R.id.warned);
banned = view.findViewById(R.id.banned);
donor.setVisibility(View.GONE);
warned.setVisibility(View.GONE);
banned.setVisibility(View.GONE);
dataDownloadedText = (TextView) view.findViewById(R.id.data_downloaded_text);
dataDownloaded = (TextView) view.findViewById(R.id.data_downloaded);
dataUploadedText = (TextView) view.findViewById(R.id.data_uploaded_text);
dataUploaded = (TextView) view.findViewById(R.id.data_uploaded);
torrentsUploadedText = (TextView) view.findViewById(R.id.torrents_uploaded_text);
torrentsUploaded = (TextView) view.findViewById(R.id.torrents_uploaded);
requestsFilledText = (TextView) view.findViewById(R.id.requests_filled_text);
requestsFilled = (TextView) view.findViewById(R.id.requests_filled);
bountySpentText = (TextView) view.findViewById(R.id.bounty_spent_text);
bountySpent = (TextView) view.findViewById(R.id.bounty_spent);
postsMadeText = (TextView) view.findViewById(R.id.posts_made_text);
postsMade = (TextView) view.findViewById(R.id.posts_made);
artistsAddedText = (TextView) view.findViewById(R.id.artists_added_text);
artistsAdded = (TextView) view.findViewById(R.id.artists_added);
overallText = (TextView) view.findViewById(R.id.overall_text);
overall = (TextView) view.findViewById(R.id.overall);
dataDownloadedValue = (TextView) view.findViewById(R.id.data_downloaded_value);
dataUploadedValue = (TextView) view.findViewById(R.id.data_uploaded_value);
postsMadeValue = (TextView) view.findViewById(R.id.posts_made_value);
requestsFilledValue = (TextView) view.findViewById(R.id.requests_filled_value);
torrentsUploadedValue = (TextView) view.findViewById(R.id.torrents_uploaded_value);
ranksView = (RelativeLayout) view.findViewById(R.id.user_ranks);
statsView = (RelativeLayout) view.findViewById(R.id.user_stats);
snatchesView = (WrappingViewPager) view.findViewById(R.id.recent_snatches);
uploadsView = (WrappingViewPager) view.findViewById(R.id.recent_uploads);
toggleStatsBtn = (ImageView) view.findViewById(R.id.toggle_stats);
toggleRanksBtn = (ImageView) view.findViewById(R.id.toggle_ranks);
toggleSnatchesBtn = (ImageView) view.findViewById(R.id.toggle_snatches);
toggleUploadsBtn = (ImageView) view.findViewById(R.id.toggle_uploads);
toggleStats = (RelativeLayout) view.findViewById(R.id.info_box);
toggleStats.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (statsView.isShown()) {
statsView.setVisibility(View.GONE);
toggleStatsBtn.setImageResource(R.drawable.ic_expand_more_24dp);
} else if (!statsView.isShown()) {
statsView.setVisibility(View.VISIBLE);
toggleStatsBtn.setImageResource(R.drawable.ic_expand_less_24dp);
}
}
});
toggleStatsBtn.setImageResource(R.drawable.ic_expand_more_24dp);
toggleRanks = (RelativeLayout) view.findViewById(R.id.ranks_header_container);
toggleRanks.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ranksView.isShown()) {
ranksView.setVisibility(View.GONE);
toggleRanksBtn.setImageResource(R.drawable.ic_expand_more_24dp);
} else if (!ranksView.isShown()) {
ranksView.setVisibility(View.VISIBLE);
toggleRanksBtn.setImageResource(R.drawable.ic_expand_less_24dp);
}
}
});
toggleRanksBtn.setImageResource(R.drawable.ic_expand_more_24dp);
toggleSnatches = (RelativeLayout) view.findViewById(R.id.snatches_header_container);
toggleSnatches.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (snatchesView.isShown()) {
snatchesView.setVisibility(View.GONE);
toggleSnatchesBtn.setImageResource(R.drawable.ic_expand_more_24dp);
} else {
snatchesView.setVisibility(View.VISIBLE);
toggleSnatchesBtn.setImageResource(R.drawable.ic_expand_less_24dp);
}
}
});
toggleSnatchesBtn.setImageResource(R.drawable.ic_expand_more_24dp);
toggleUploads = (RelativeLayout) view.findViewById(R.id.uploads_header_container);
toggleUploads.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (uploadsView.isShown()) {
uploadsView.setVisibility(View.GONE);
toggleUploadsBtn.setImageResource(R.drawable.ic_expand_more_24dp);
} else {
uploadsView.setVisibility(View.VISIBLE);
toggleUploadsBtn.setImageResource(R.drawable.ic_expand_less_24dp);
}
}
});
toggleUploadsBtn.setImageResource(R.drawable.ic_expand_more_24dp);
snatchesAdapter = new RecentTorrentPagerAdapter(getChildFragmentManager());
uploadsAdapter = new RecentTorrentPagerAdapter(getChildFragmentManager());
recentSnatches.setAdapter(snatchesAdapter);
recentUploads.setAdapter(uploadsAdapter);
if (MySoup.isLoggedIn() && !deferLoad) {
//We could get -1 user id if we were logged out and trying to view our own profile, so update it
if (userID == -1) {
userID = MySoup.getUserId();
getArguments().putInt(ProfileActivity.USER_ID, userID);
}
Bundle args = new Bundle();
args.putInt(ProfileActivity.USER_ID, userID);
getLoaderManager().initLoader(0, args, this);
}
return view;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(ReplyDialogFragment.DRAFT, messageDraft);
outState.putString(ReplyDialogFragment.SUBJECT, messageSubject);
}
@Override
public void onLoggedIn() {
if (isAdded() && !deferLoad) {
//We could get -1 user id if we were logged out and trying to view our own profile, so update it
if (userID == -1) {
userID = MySoup.getUserId();
getArguments().putInt(ProfileActivity.USER_ID, userID);
}
Bundle args = new Bundle();
args.putInt(ProfileActivity.USER_ID, userID);
getLoaderManager().initLoader(0, args, this);
}
}
@Override
public Loader<UserProfile> onCreateLoader(int id, Bundle args) {
if (isAdded()) {
getActivity().setProgressBarIndeterminate(true);
getActivity().setProgressBarIndeterminateVisibility(true);
}
return new ProfileAsyncLoader(getActivity(), args);
}
@Override
public void onLoadFinished(Loader<UserProfile> loader, UserProfile data) {
getActivity().setProgressBarIndeterminate(false);
getActivity().setProgressBarIndeterminateVisibility(false);
if (data == null || !data.getStatus()) {
Toast.makeText(getActivity(), "Could not load profile", Toast.LENGTH_LONG).show();
} else {
userProfile = data;
populateViews();
if (indexLoadingListener != null) {
indexLoadingListener.onLoadingComplete(MySoup.getIndex());
}
if (userID != MySoup.getUserId() && sendMessage != null) {
sendMessage.setVisible(true);
}
}
}
@Override
public void onLoaderReset(Loader<UserProfile> loader) {
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.profile, menu);
sendMessage = menu.findItem(R.id.action_message);
if (userProfile != null && userID != MySoup.getUserId()) {
sendMessage.setVisible(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_refresh) {
Bundle args = new Bundle();
args.putInt(ProfileActivity.USER_ID, userID);
getLoaderManager().restartLoader(0, args, this);
}
if (item.getItemId() == R.id.action_message) {
showReplyDialog();
}
return false;
}
@Override
public void post(String message, String subject) {
messageDraft = "";
messageSubject = "";
new SendMessageTask().execute(subject, message);
}
@Override
public void saveDraft(String message, String subject) {
messageDraft = message;
messageSubject = subject;
}
@Override
public void discard() {
messageDraft = "";
messageSubject = "";
}
/**
* Display the compose reply dialog so the user can write their response
*/
private void showReplyDialog() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
ReplyDialogFragment reply = ReplyDialogFragment.newInstance(messageDraft, messageSubject);
reply.setTargetFragment(this, 0);
reply.show(ft, "dialog");
}
/**
* Update the profile fields with the information we loaded. We need to do a ton of checks here to
* properly handle user's various paranoia configurations, which could cause us to get a null for any of the
* fields that can be hidden. We also hide the recent snatches/uploads if the user's paranoia is high (6+).
* When viewing our own profile we'll get all the data back but will still see our paranoia value so we need to
* ignore the paranoia if it's our own profile
*/
private void populateViews() {
Profile profile = userProfile.getUser().getProfile();
setTitle.setTitle(profile.getUsername());
username.setText(profile.getUsername());
userClass.setText(profile.getPersonal().getUserClass());
Date joinDate = MySoup.parseDate(profile.getStats().getJoinedDate());
joined.setText("Joined " + DateUtils.getRelativeTimeSpanString(joinDate.getTime(),
new Date().getTime(), DateUtils.WEEK_IN_MILLIS));
//We need to check all the paranoia cases that may cause a field to be missing and hide the views for it
String avatarUrl = profile.getAvatar();
if (!SettingsActivity.imagesEnabled(getActivity())) {
artContainer.setVisibility(View.GONE);
} else {
artContainer.setVisibility(View.VISIBLE);
WhatApplication.loadImage(getActivity(), avatarUrl, avatar, spinner, null, null);
}
if (profile.getPersonal().getParanoia().intValue() > 0 && userID != MySoup.getUserId()) {
paranoiaText.setVisibility(View.VISIBLE);
paranoia.setText(profile.getPersonal().getParanoiaText());
} else {
paranoia.setVisibility(View.GONE);
}
if (profile.getCommunity().getInvited() != null) {
invited.setText("" + profile.getCommunity().getInvited());
} else {
invitedText.setVisibility(View.GONE);
invited.setVisibility(View.GONE);
}
if (profile.getStats().getRatio() != null && profile.getStats().getRequiredRatio() != null) {
ratio.setText(String.format("%.2f", profile.getStats().getRatio().floatValue())
+ " / " + String.format("%.2f", profile.getStats().getRequiredRatio().floatValue()));
} else {
ratioText.setVisibility(View.GONE);
ratio.setVisibility(View.GONE);
}
//TODO: Keep an eye on this API endpoint and watch for when it starts respecting paranoia and we get null back
UserRecents recentTorrents = userProfile.getUserRecents();
if (profile.getPersonal().getParanoia().intValue() < 6 || userID == MySoup.getUserId()) {
if (recentTorrents.getSnatches().size() > 0) {
snatchesAdapter.onLoadingComplete(recentTorrents.getSnatches());
snatchesAdapter.notifyDataSetChanged();
} else {
snatchesContainer.setVisibility(View.GONE);
}
if (recentTorrents.getUploads().size() > 0) {
uploadsAdapter.onLoadingComplete(recentTorrents.getUploads());
uploadsAdapter.notifyDataSetChanged();
} else {
uploadsContainer.setVisibility(View.GONE);
}
} else {
snatchesContainer.setVisibility(View.GONE);
uploadsContainer.setVisibility(View.GONE);
}
if (profile.getPersonal().isDonor()) {
donor.setVisibility(View.VISIBLE);
}
if (profile.getPersonal().isWarned()) {
warned.setVisibility(View.VISIBLE);
}
if (!profile.getPersonal().isEnabled()) {
banned.setVisibility(View.VISIBLE);
}
if (profile.getRanks().getDownloaded() != null) {
dataDownloadedText.setVisibility(View.VISIBLE);
dataDownloaded.setText("" + profile.getRanks().getDownloaded().intValue() + "%");
dataDownloadedValue.setVisibility(View.VISIBLE);
dataDownloadedValue.setText("(" + Utils.toHumanReadableSize(profile.getStats().getDownloaded().longValue()) + ") ");
} else {
dataDownloaded.setVisibility(View.GONE);
dataDownloadedText.setVisibility(View.GONE);
}
if (profile.getRanks().getUploaded() != null) {
dataUploadedText.setVisibility(View.VISIBLE);
dataUploaded.setText("" + profile.getRanks().getUploaded().intValue() + "%");
dataUploadedValue.setVisibility(View.VISIBLE);
dataUploadedValue.setText("(" + Utils.toHumanReadableSize(profile.getStats().getUploaded().longValue()) + ") ");
} else {
dataUploaded.setVisibility(View.GONE);
dataUploadedText.setVisibility(View.GONE);
}
if (profile.getRanks().getUploads() != null) {
torrentsUploadedText.setVisibility(View.VISIBLE);
torrentsUploaded.setText("" + profile.getRanks().getUploads().intValue() + "%");
torrentsUploadedValue.setVisibility(View.VISIBLE);
torrentsUploadedValue.setText("(" + profile.getCommunity().getUploaded().intValue() + ") ");
} else {
torrentsUploaded.setVisibility(View.GONE);
torrentsUploadedText.setVisibility(View.GONE);
}
if (profile.getRanks().getRequests() != null) {
requestsFilledText.setVisibility(View.VISIBLE);
requestsFilled.setText("" + profile.getRanks().getRequests().intValue() + "%");
requestsFilledValue.setVisibility(View.VISIBLE);
requestsFilledValue.setText("(" + profile.getCommunity().getRequestsFilled().intValue() + ") ");
} else {
requestsFilled.setVisibility(View.GONE);
requestsFilledText.setVisibility(View.GONE);
}
if (profile.getRanks().getBounty() != null) {
bountySpentText.setVisibility(View.VISIBLE);
bountySpent.setText("" + profile.getRanks().getBounty().intValue() + "%");
} else {
bountySpent.setVisibility(View.GONE);
bountySpentText.setVisibility(View.GONE);
}
if (profile.getRanks().getPosts() != null) {
postsMadeText.setVisibility(View.VISIBLE);
postsMade.setText("" + profile.getRanks().getPosts().intValue() + "%");
postsMadeValue.setVisibility(View.VISIBLE);
postsMadeValue.setText("(" + profile.getCommunity().getPosts().intValue() + ") ");
} else {
postsMade.setVisibility(View.GONE);
postsMadeText.setVisibility(View.GONE);
}
if (profile.getRanks().getArtists() != null) {
artistsAddedText.setVisibility(View.VISIBLE);
artistsAdded.setText("" + profile.getRanks().getArtists().intValue() + "%");
} else {
artistsAdded.setVisibility(View.GONE);
artistsAddedText.setVisibility(View.GONE);
}
if (profile.getRanks().getOverall() != null) {
overallText.setVisibility(View.VISIBLE);
overall.setText("" + profile.getRanks().getOverall().intValue() + "%");
} else {
overall.setVisibility(View.GONE);
overallText.setVisibility(View.GONE);
}
}
private class SendMessageTask extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... params) {
return User.sendMessage(userID, params[0], params[1]);
}
@Override
protected void onPostExecute(Boolean status) {
if (!status) {
Toast.makeText(getActivity(), "Could not send message", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), "Message sent", Toast.LENGTH_SHORT).show();
}
}
}
}