package com.boardgamegeek.ui; import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.ContextCompat; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v7.graphics.Palette; import android.support.v7.graphics.Palette.Swatch; import android.support.v7.widget.CardView; import android.text.TextUtils; import android.transition.AutoTransition; import android.transition.Transition; import android.transition.TransitionManager; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.boardgamegeek.R; import com.boardgamegeek.auth.Authenticator; import com.boardgamegeek.events.CollectionItemUpdatedEvent; import com.boardgamegeek.events.GameInfoChangedEvent; import com.boardgamegeek.io.Adapter; import com.boardgamegeek.io.BggService; import com.boardgamegeek.model.Forum; import com.boardgamegeek.model.ForumListResponse; import com.boardgamegeek.provider.BggContract.Artists; import com.boardgamegeek.provider.BggContract.Categories; import com.boardgamegeek.provider.BggContract.Collection; import com.boardgamegeek.provider.BggContract.Designers; import com.boardgamegeek.provider.BggContract.GamePollResultsResult; import com.boardgamegeek.provider.BggContract.GamePolls; import com.boardgamegeek.provider.BggContract.GameRanks; import com.boardgamegeek.provider.BggContract.Games; import com.boardgamegeek.provider.BggContract.GamesExpansions; import com.boardgamegeek.provider.BggContract.Mechanics; import com.boardgamegeek.provider.BggContract.Plays; import com.boardgamegeek.provider.BggContract.Publishers; import com.boardgamegeek.service.SyncService; import com.boardgamegeek.service.UpdateService; import com.boardgamegeek.tasks.AddCollectionItemTask; import com.boardgamegeek.ui.adapter.GameColorAdapter; import com.boardgamegeek.ui.dialog.CollectionStatusDialogFragment; import com.boardgamegeek.ui.dialog.CollectionStatusDialogFragment.CollectionStatusDialogListener; import com.boardgamegeek.ui.widget.GameCollectionRow; import com.boardgamegeek.ui.widget.GameDetailRow; import com.boardgamegeek.ui.widget.SafeViewTarget; import com.boardgamegeek.ui.widget.StatBar; import com.boardgamegeek.ui.widget.TimestampView; import com.boardgamegeek.util.ActivityUtils; import com.boardgamegeek.util.AnimationUtils; import com.boardgamegeek.util.ColorUtils; import com.boardgamegeek.util.CursorUtils; import com.boardgamegeek.util.DateTimeUtils; import com.boardgamegeek.util.DialogUtils; import com.boardgamegeek.util.HelpUtils; import com.boardgamegeek.util.PaletteUtils; import com.boardgamegeek.util.PreferencesUtils; import com.boardgamegeek.util.PresentationUtils; import com.boardgamegeek.util.SelectionBuilder; import com.boardgamegeek.util.ShowcaseViewWizard; import com.boardgamegeek.util.TaskUtils; import com.boardgamegeek.util.UIUtils; import com.github.amlcurran.showcaseview.targets.Target; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import java.util.ArrayList; import java.util.List; import butterknife.BindView; import butterknife.BindViews; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; import hugo.weaving.DebugLog; import icepick.Icepick; import icepick.State; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import timber.log.Timber; public class GameFragment extends Fragment implements LoaderCallbacks<Cursor> { private static final int HELP_VERSION = 2; private static final int AGE_IN_DAYS_TO_REFRESH = 7; private static final String POLL_TYPE_LANGUAGE_DEPENDENCE = "language_dependence"; private Uri gameUri; private String gameName; private String imageUrl; private String thumbnailUrl; private boolean arePlayersCustomSorted; @ColorInt private int iconColor; private Unbinder unbinder; @BindView(R.id.game_rating) TextView ratingView; @BindView(R.id.description_card) CardView descriptionCard; @BindView(R.id.game_description) TextView descriptionView; @BindView(R.id.game_rank) TextView rankView; @BindView(R.id.game_year_published) TextView yearPublishedView; @BindView(R.id.primary_info_container) View primaryInfoContainer; @BindView(R.id.number_of_players) TextView numberOfPlayersView; @BindView(R.id.play_time) TextView playTimeView; @BindView(R.id.player_age) TextView playerAgeView; @BindView(R.id.game_subtype) TextView subtypeView; @BindView(R.id.game_subtype_container) ViewGroup subtypeContainer; @BindView(R.id.subtype_expander) ImageView subtypeExpander; @BindView(R.id.game_info_designers) GameDetailRow designersView; @BindView(R.id.game_info_artists) GameDetailRow artistsView; @BindView(R.id.game_info_publishers) GameDetailRow publishersView; @BindView(R.id.game_info_categories) GameDetailRow categoriesView; @BindView(R.id.game_info_mechanics) GameDetailRow mechanicsView; @BindView(R.id.game_info_expansions) GameDetailRow expansionsView; @BindView(R.id.game_info_base_games) GameDetailRow baseGamesView; @BindView(R.id.collection_card) View collectionCard; @BindView(R.id.collection_container) ViewGroup collectionContainer; @BindView(R.id.collection_add_button) TextView collectionAddButton; @BindView(R.id.plays_card) View playsCard; @BindView(R.id.plays_root) View playsRoot; @BindView(R.id.plays_label) TextView playsLabel; @BindView(R.id.plays_last_play) TextView lastPlayView; @BindView(R.id.play_stats_root) View playStatsRoot; @BindView(R.id.colors_root) View colorsRoot; @BindView(R.id.game_colors_label) TextView colorsLabel; @BindView(R.id.game_ratings_label) TextView ratingsLabel; @BindView(R.id.game_ratings_votes) TextView ratingsVotes; @BindView(R.id.game_ratings_standard_deviation) TextView ratingsStandardDeviation; @BindView(R.id.game_comments_label) TextView commentsLabel; @BindView(R.id.forums_last_post_date) TimestampView forumsLastPostDateView; @BindView(R.id.weight_root) View weightRoot; @BindView(R.id.game_weight) TextView weightView; @BindView(R.id.game_weight_votes) TextView weightVotes; @BindView(R.id.language_dependence_root) View languageDependenceRoot; @BindView(R.id.language_dependence_details) TextView languageDependenceDetails; @BindView(R.id.users_count) TextView userCountView; @BindView(R.id.users_owning_bar) StatBar numberOwningBar; @BindView(R.id.users_trading_bar) StatBar numberTradingBar; @BindView(R.id.users_wanting_bar) StatBar numberWantingBar; @BindView(R.id.users_wishing_bar) StatBar numberWishingBar; @BindView(R.id.game_info_id) TextView idView; @BindView(R.id.game_info_last_updated) TimestampView updatedView; @BindViews({ R.id.game_year_published, R.id.number_of_players, R.id.play_time, R.id.player_age }) List<TextView> colorizedTextViews; @BindViews({ R.id.game_info_designers, R.id.game_info_artists, R.id.game_info_publishers, R.id.game_info_categories, R.id.game_info_mechanics, R.id.game_info_expansions, R.id.game_info_base_games }) List<GameDetailRow> colorizedRows; @BindViews({ R.id.card_header_details, R.id.card_header_collection, R.id.card_header_plays, R.id.card_header_user_feedback, R.id.card_header_links }) List<TextView> colorizedHeaders; @BindViews({ R.id.icon_plays, R.id.icon_play_stats, R.id.icon_colors, R.id.icon_forums, R.id.icon_comments, R.id.icon_ratings, R.id.icon_weight, R.id.icon_language_dependence, R.id.icon_users, R.id.icon_link_bgg, R.id.icon_link_bg_prices, R.id.icon_link_amazon, R.id.icon_link_ebay }) List<ImageView> colorizedIcons; @BindViews({ R.id.collection_add_button }) List<Button> colorizedButtons; @BindViews({ R.id.users_owning_bar, R.id.users_trading_bar, R.id.users_wanting_bar, R.id.users_wishing_bar, }) List<StatBar> statBars; @State boolean isRanksExpanded; @State boolean isDescriptionExpanded; private boolean mightNeedRefreshing; private Palette palette; private ShowcaseViewWizard showcaseViewWizard; private Transition expansionTransition; @Override @DebugLog public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Icepick.restoreInstanceState(this, savedInstanceState); setHasOptionsMenu(true); final Intent intent = UIUtils.fragmentArgumentsToIntent(getArguments()); gameUri = intent.getData(); } @DebugLog @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } @Override @DebugLog public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_game, container, false); unbinder = ButterKnife.bind(this, rootView); colorize(); openOrCloseDescription(); mightNeedRefreshing = true; LoaderManager lm = getLoaderManager(); lm.restartLoader(GameQuery._TOKEN, null, this); lm.restartLoader(RankQuery._TOKEN, null, this); if (shouldShowPlays()) { lm.restartLoader(PlaysQuery._TOKEN, null, this); } if (PreferencesUtils.showLogPlay(getActivity())) { lm.restartLoader(ColorQuery._TOKEN, null, this); } lm.restartLoader(LanguagePollQuery._TOKEN, null, this); if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { expansionTransition = new AutoTransition(); expansionTransition.setDuration(150); AnimationUtils.setInterpolator(getContext(), expansionTransition); } showcaseViewWizard = setUpShowcaseViewWizard(); showcaseViewWizard.maybeShowHelp(); return rootView; } @Override @DebugLog public void onDestroyView() { super.onDestroyView(); unbinder.unbind(); } @Override @DebugLog public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.help, menu); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.menu_help) { showcaseViewWizard.showHelp(); return true; } return super.onOptionsItemSelected(item); } @NonNull private ShowcaseViewWizard setUpShowcaseViewWizard() { ShowcaseViewWizard wizard = new ShowcaseViewWizard(getActivity(), HelpUtils.HELP_GAME_KEY, HELP_VERSION); wizard.addTarget(R.string.help_game_menu, Target.NONE); wizard.addTarget(R.string.help_game_log_play, new SafeViewTarget(R.id.fab, getActivity())); wizard.addTarget(R.string.help_game_poll, new SafeViewTarget(R.id.number_of_players, getActivity())); wizard.addTarget(-1, new SafeViewTarget(R.id.player_age, getActivity())); return wizard; } @Override @DebugLog public Loader<Cursor> onCreateLoader(int id, Bundle data) { CursorLoader loader = null; int gameId = Games.getGameId(gameUri); switch (id) { case GameQuery._TOKEN: loader = new CursorLoader(getActivity(), gameUri, GameQuery.PROJECTION, null, null, null); break; case DesignerQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildDesignersUri(gameId), DesignerQuery.PROJECTION, null, null, null); break; case ArtistQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildArtistsUri(gameId), ArtistQuery.PROJECTION, null, null, null); break; case PublisherQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildPublishersUri(gameId), PublisherQuery.PROJECTION, null, null, null); break; case CategoryQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildCategoriesUri(gameId), CategoryQuery.PROJECTION, null, null, null); break; case MechanicQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildMechanicsUri(gameId), MechanicQuery.PROJECTION, null, null, null); break; case ExpansionQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildExpansionsUri(gameId), ExpansionQuery.PROJECTION, GamesExpansions.INBOUND + "=?", new String[] { "0" }, null); break; case BaseGameQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildExpansionsUri(gameId), BaseGameQuery.PROJECTION, GamesExpansions.INBOUND + "=?", new String[] { "1" }, null); break; case RankQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildRanksUri(gameId), RankQuery.PROJECTION, null, null, null); break; case CollectionQuery._TOKEN: loader = new CursorLoader(getActivity(), Collection.CONTENT_URI, CollectionQuery.PROJECTION, "collection." + Collection.GAME_ID + "=?", new String[] { String.valueOf(gameId) }, null); break; case PlaysQuery._TOKEN: // retrieve plays that aren't pending delete (optionally only completed plays) String selection = String.format("%s=? AND %s", Plays.OBJECT_ID, SelectionBuilder.whereZeroOrNull(Plays.DELETE_TIMESTAMP)); if (!PreferencesUtils.logPlayStatsIncomplete(getActivity())) { selection += String.format(" AND %s!=1", Plays.INCOMPLETE); } loader = new CursorLoader(getActivity(), Plays.CONTENT_URI, PlaysQuery.PROJECTION, selection, new String[] { String.valueOf(gameId) }, null); break; case ColorQuery._TOKEN: loader = new CursorLoader(getActivity(), GameColorAdapter.createUri(gameId), GameColorAdapter.PROJECTION, null, null, null); break; case LanguagePollQuery._TOKEN: loader = new CursorLoader(getActivity(), Games.buildPollResultsResultUri(gameId, POLL_TYPE_LANGUAGE_DEPENDENCE), LanguagePollQuery.PROJECTION, null, null, LanguagePollQuery.SORT); break; default: Timber.w("Invalid query token=%s", id); break; } return loader; } @Override @DebugLog public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { if (getActivity() == null) { return; } switch (loader.getId()) { case GameQuery._TOKEN: onGameQueryComplete(cursor); LoaderManager lm = getLoaderManager(); if (shouldShowCollection()) lm.restartLoader(CollectionQuery._TOKEN, null, this); lm.restartLoader(DesignerQuery._TOKEN, null, this); lm.restartLoader(ArtistQuery._TOKEN, null, this); lm.restartLoader(PublisherQuery._TOKEN, null, this); lm.restartLoader(CategoryQuery._TOKEN, null, this); lm.restartLoader(MechanicQuery._TOKEN, null, this); lm.restartLoader(ExpansionQuery._TOKEN, null, this); lm.restartLoader(BaseGameQuery._TOKEN, null, this); fetchForumInfo(); break; case DesignerQuery._TOKEN: onListQueryComplete(cursor, designersView, DesignerQuery.DESIGNER_NAME, DesignerQuery.DESIGNER_ID); break; case ArtistQuery._TOKEN: onListQueryComplete(cursor, artistsView, ArtistQuery.ARTIST_NAME, ArtistQuery.ARTIST_ID); break; case PublisherQuery._TOKEN: onListQueryComplete(cursor, publishersView, PublisherQuery.PUBLISHER_NAME, PublisherQuery.PUBLISHER_ID); break; case CategoryQuery._TOKEN: onListQueryComplete(cursor, categoriesView, CategoryQuery.CATEGORY_NAME, CategoryQuery.CATEGORY_ID); break; case MechanicQuery._TOKEN: onListQueryComplete(cursor, mechanicsView, MechanicQuery.MECHANIC_NAME, MechanicQuery.MECHANIC_ID); break; case ExpansionQuery._TOKEN: onListQueryComplete(cursor, expansionsView, ExpansionQuery.EXPANSION_NAME, ExpansionQuery.EXPANSION_ID); break; case BaseGameQuery._TOKEN: onListQueryComplete(cursor, baseGamesView, BaseGameQuery.EXPANSION_NAME, BaseGameQuery.EXPANSION_ID); break; case RankQuery._TOKEN: onRankQueryComplete(cursor); break; case CollectionQuery._TOKEN: onCollectionQueryComplete(cursor); break; case PlaysQuery._TOKEN: onPlaysQueryComplete(cursor); break; case ColorQuery._TOKEN: playsCard.setVisibility(View.VISIBLE); colorsRoot.setVisibility(View.VISIBLE); int count = cursor == null ? 0 : cursor.getCount(); colorsLabel.setText(PresentationUtils.getQuantityText(getActivity(), R.plurals.colors_suffix, count, count)); break; case LanguagePollQuery._TOKEN: onLanguagePollQueryComplete(cursor); break; default: cursor.close(); break; } } private void fetchForumInfo() { if (forumsLastPostDateView.getVisibility() == View.VISIBLE) { return; } BggService bggService = Adapter.createForXml(); Call<ForumListResponse> call = bggService.forumList(BggService.FORUM_TYPE_THING, Games.getGameId(gameUri)); call.enqueue(new Callback<ForumListResponse>() { @Override public void onResponse(Call<ForumListResponse> call, Response<ForumListResponse> response) { if (response.isSuccessful() && forumsLastPostDateView != null) { long lastPostDate = 0; String title = ""; for (Forum forum : response.body().getForums()) { if (forum.lastPostDate() > lastPostDate) { lastPostDate = forum.lastPostDate(); title = forum.title; } } forumsLastPostDateView.setFormatArg(title); forumsLastPostDateView.setTimestamp(lastPostDate); } } @Override public void onFailure(Call<ForumListResponse> call, Throwable t) { Timber.w("Failed fetching forum for game %s: %s", Games.getGameId(gameUri), t.getMessage()); } }); } @Override @DebugLog public void onLoaderReset(Loader<Cursor> loader) { } @DebugLog public void onPaletteGenerated(Palette palette) { this.palette = palette; colorize(); } @DebugLog private void colorize() { if (palette == null || primaryInfoContainer == null || !isAdded()) { return; } Palette.Swatch swatch = PaletteUtils.getInverseSwatch(palette, ContextCompat.getColor(getContext(), R.color.info_background)); primaryInfoContainer.setBackgroundColor(swatch.getRgb()); ButterKnife.apply(colorizedTextViews, PaletteUtils.colorTextViewOnBackgroundSetter, swatch); swatch = PaletteUtils.getIconSwatch(palette); iconColor = swatch.getRgb(); ButterKnife.apply(colorizedRows, GameDetailRow.colorIconSetter, swatch); ButterKnife.apply(colorizedIcons, PaletteUtils.colorIconSetter, swatch); ButterKnife.apply(colorizedButtons, PaletteUtils.colorButtonSetter, swatch); ButterKnife.apply(colorizedHeaders, PaletteUtils.colorTextViewSetter, PaletteUtils.getHeaderSwatch(palette)); ButterKnife.apply(statBars, StatBar.colorSetter, PaletteUtils.getDarkSwatch(palette)); } @DebugLog private void onGameQueryComplete(Cursor cursor) { if (cursor == null || !cursor.moveToFirst()) { if (mightNeedRefreshing) { triggerRefresh(); } return; } Game game = new Game(cursor); notifyChange(game); gameName = game.Name; imageUrl = game.ImageUrl; thumbnailUrl = game.ThumbnailUrl; arePlayersCustomSorted = game.CustomPlayerSort; rankView.setText(game.getRankDescription()); yearPublishedView.setText(game.getYearPublished()); subtypeView.setText(game.getSubtype()); ratingView.setText(game.getRatingDescription()); ColorUtils.setViewBackground(ratingView, ColorUtils.getRatingColor(game.Rating)); idView.setText(String.valueOf(game.Id)); updatedView.setTimestamp(game.Updated); UIUtils.setTextMaybeHtml(descriptionView, game.Description); numberOfPlayersView.setText(game.getPlayerRangeDescription()); playTimeView.setText(game.getPlayingTimeDescription()); playerAgeView.setText(game.getAgeDescription()); commentsLabel.setText(PresentationUtils.getQuantityText(getActivity(), R.plurals.comments_suffix, game.UsersCommented, game.UsersCommented)); ratingsLabel.setText(PresentationUtils.getText(getActivity(), R.string.average_rating_prefix, PresentationUtils.describeAverageRating(getActivity(), game.Rating))); ratingsVotes.setText(PresentationUtils.getQuantityText(getActivity(), R.plurals.votes_suffix, game.UsersRated, game.UsersRated)); ratingsStandardDeviation.setText(getString(R.string.standard_deviation_prefix, PresentationUtils.describeAverageRating(getActivity(), game.StandardDeviation))); weightRoot.setVisibility(game.NumberWeights > 0 ? View.VISIBLE : View.GONE); weightView.setText(PresentationUtils.describeWeight(getActivity(), game.AverageWeight)); weightVotes.setText(PresentationUtils.getQuantityText(getActivity(), R.plurals.votes_suffix, game.NumberWeights, game.NumberWeights)); final int maxUsers = game.getMaxUsers(); userCountView.setText(PresentationUtils.getQuantityText(getActivity(), R.plurals.users_suffix, maxUsers, maxUsers)); numberOwningBar.setBar(R.string.owning_meter_text, game.NumberOwned, maxUsers); numberTradingBar.setBar(R.string.trading_meter_text, game.NumberTrading, maxUsers); numberWantingBar.setBar(R.string.wanting_meter_text, game.NumberWanting, maxUsers); numberWishingBar.setBar(R.string.wishing_meter_text, game.NumberWishing, maxUsers); if (shouldShowPlays()) { playsCard.setVisibility(View.VISIBLE); playStatsRoot.setVisibility(View.VISIBLE); } if (mightNeedRefreshing && (game.PollsCount == 0 || DateTimeUtils.howManyDaysOld(game.Updated) > AGE_IN_DAYS_TO_REFRESH)) { triggerRefresh(); } mightNeedRefreshing = false; } @DebugLog private boolean shouldShowCollection() { String[] syncStatuses = PreferencesUtils.getSyncStatuses(getContext()); return Authenticator.isSignedIn(getActivity()) && syncStatuses != null && syncStatuses.length > 0; } @DebugLog private boolean shouldShowPlays() { return Authenticator.isSignedIn(getActivity()) && PreferencesUtils.getSyncPlays(getContext()); } @DebugLog private void notifyChange(Game game) { GameInfoChangedEvent event = new GameInfoChangedEvent(game.Name, game.Subtype, game.ImageUrl, game.ThumbnailUrl, game.CustomPlayerSort); EventBus.getDefault().post(event); } @DebugLog private void onListQueryComplete(Cursor cursor, GameDetailRow view, int nameColumnIndex, int idColumnIndex) { if (cursor == null || !cursor.moveToFirst()) { view.setVisibility(View.GONE); view.clear(); } else { view.setVisibility(View.VISIBLE); view.bind(cursor, nameColumnIndex, idColumnIndex, Games.getGameId(gameUri), gameName); } } @DebugLog private void onRankQueryComplete(Cursor cursor) { if (subtypeContainer != null) { subtypeContainer.removeAllViews(); if (cursor != null && cursor.getCount() > 0) { while (cursor.moveToNext()) { Rank rank = new Rank(cursor); if (!"subtype".equals(rank.Type)) { addRankRow(rank.Name, rank.Rank, rank.Rating); } } } } } @DebugLog private void addRankRow(String label, int rank, double rating) { LinearLayout layout = (LinearLayout) getLayoutInflater(null).inflate(R.layout.widget_rank_row, subtypeContainer, false); TextView tv = (TextView) layout.findViewById(R.id.rank_row_label); tv.setText(PresentationUtils.describeRankName(getActivity(), "family", label)); tv = (TextView) layout.findViewById(R.id.rank_row_rank); tv.setText(PresentationUtils.describeRank(rank)); tv = (TextView) layout.findViewById(R.id.rank_row_rating); tv.setText(PresentationUtils.describeAverageRating(getActivity(), rating)); subtypeContainer.addView(layout); } @DebugLog private void onCollectionQueryComplete(Cursor cursor) { collectionCard.setVisibility(View.VISIBLE); collectionContainer.removeViews(2, collectionContainer.getChildCount() - 2); if (cursor != null && cursor.moveToFirst()) { collectionAddButton.setVisibility(View.GONE); do { GameCollectionRow row = new GameCollectionRow(getActivity()); final long internalId = cursor.getLong(CollectionQuery._ID); final int gameId = Games.getGameId(gameUri); final int collectionId = cursor.getInt(CollectionQuery.COLLECTION_ID); final int yearPublished = cursor.getInt(CollectionQuery.YEAR_PUBLISHED); final String imageUrl = cursor.getString(CollectionQuery.COLLECTION_IMAGE_URL); row.bind(internalId, gameId, gameName, collectionId, yearPublished, imageUrl); final String thumbnailUrl = cursor.getString(CollectionQuery.COLLECTION_THUMBNAIL_URL); final String collectionName = cursor.getString(CollectionQuery.COLLECTION_NAME); final int collectionYearPublished = cursor.getInt(CollectionQuery.COLLECTION_YEAR); final int numberOfPlays = cursor.getInt(CollectionQuery.NUM_PLAYS); final String comment = cursor.getString(CollectionQuery.COMMENT); final double rating = cursor.getDouble(CollectionQuery.RATING); List<String> status = new ArrayList<>(); for (int i = CollectionQuery.STATUS_1; i <= CollectionQuery.STATUS_N; i++) { if (cursor.getInt(i) == 1) { if (i == CollectionQuery.STATUS_WISHLIST) { status.add(PresentationUtils.describeWishlist(getActivity(), cursor.getInt(CollectionQuery.STATUS_WISHLIST_PRIORITY))); } else { int index = i - CollectionQuery.STATUS_1; status.add(getResources().getStringArray(R.array.collection_status_filter_entries)[index]); } } } row.setThumbnail(thumbnailUrl); row.setStatus(status, numberOfPlays, rating, comment); row.setDescription(collectionName, collectionYearPublished); row.setComment(comment); row.setRating(rating); collectionContainer.addView(row); } while (cursor.moveToNext()); } else { collectionAddButton.setVisibility(View.VISIBLE); } } @OnClick(R.id.collection_add_button) void onAddToCollectionClick() { CollectionStatusDialogFragment statusDialogFragment = CollectionStatusDialogFragment.newInstance( collectionContainer, new CollectionStatusDialogListener() { @Override public void onSelectStatuses(List<String> selectedStatuses, int wishlistPriority) { int gameId = Games.getGameId(gameUri); AddCollectionItemTask task = new AddCollectionItemTask(getActivity(), gameId, selectedStatuses, wishlistPriority); TaskUtils.executeAsyncTask(task); } } ); statusDialogFragment.setTitle(R.string.title_add_a_copy); DialogUtils.showFragment(getActivity(), statusDialogFragment, "status_dialog"); } @DebugLog private void onPlaysQueryComplete(Cursor cursor) { if (cursor.moveToFirst()) { playsCard.setVisibility(View.VISIBLE); playsRoot.setVisibility(View.VISIBLE); int sum = cursor.getInt(PlaysQuery.SUM_QUANTITY); long date = CursorUtils.getDateInMillis(cursor, PlaysQuery.MAX_DATE); //if (sum > 0) { String description = PresentationUtils.describePlayCount(getActivity(), sum); if (!TextUtils.isEmpty(description)) { description = " (" + description + ")"; } playsLabel.setText(PresentationUtils.getQuantityText(getActivity(), R.plurals.plays_prefix, sum, sum, description)); //} else { // playsLabel.setText(getResources().getString(R.string.no_plays)); //} if (date > 0) { lastPlayView.setText(PresentationUtils.getText(getActivity(), R.string.last_played_prefix, PresentationUtils.describePastDaySpan(date))); lastPlayView.setVisibility(View.VISIBLE); } else { lastPlayView.setVisibility(View.GONE); } } } @DebugLog private void onLanguagePollQueryComplete(Cursor cursor) { int totalLevel = 0; int totalVotes = 0; if (cursor != null) { while (cursor.moveToNext()) { totalVotes = cursor.getInt(LanguagePollQuery.POLL_TOTAL_VOTES); int level = cursor.getInt(LanguagePollQuery.POLL_RESULTS_RESULT_LEVEL) % 5; int votes = cursor.getInt(LanguagePollQuery.POLL_RESULTS_RESULT_VOTES); totalLevel += votes * level; } } languageDependenceDetails.setText(PresentationUtils.describeLanguageDependence(getActivity(), (double) totalLevel / totalVotes)); languageDependenceRoot.setVisibility(totalVotes > 0 ? View.VISIBLE : View.GONE); } @OnClick(R.id.rank_root) @DebugLog public void onRankClick() { isRanksExpanded = !isRanksExpanded; openOrCloseRanks(); } @OnClick(R.id.game_description) @DebugLog public void onDescriptionClick() { isDescriptionExpanded = !isDescriptionExpanded; openOrCloseDescription(); } @OnClick(R.id.plays_root) @DebugLog public void onPlaysClick() { Intent intent = ActivityUtils.createGamePlaysIntent(getActivity(), gameUri, gameName, imageUrl, thumbnailUrl, arePlayersCustomSorted, iconColor); startActivity(intent); } @OnClick(R.id.play_stats_root) @DebugLog public void onPlayStatsClick() { Intent intent = new Intent(getActivity(), GamePlayStatsActivity.class); intent.setData(gameUri); intent.putExtra(ActivityUtils.KEY_GAME_NAME, gameName); if (palette != null) { final Swatch swatch = PaletteUtils.getHeaderSwatch(palette); intent.putExtra(ActivityUtils.KEY_HEADER_COLOR, swatch.getRgb()); } startActivity(intent); } @OnClick(R.id.colors_root) @DebugLog public void onColorsClick() { Intent intent = new Intent(getActivity(), GameColorsActivity.class); intent.setData(gameUri); intent.putExtra(ActivityUtils.KEY_GAME_NAME, gameName); intent.putExtra(ActivityUtils.KEY_ICON_COLOR, iconColor); startActivity(intent); } @OnClick(R.id.forums_root) @DebugLog public void onForumsClick() { Intent intent = new Intent(getActivity(), GameForumsActivity.class); intent.setData(gameUri); intent.putExtra(ActivityUtils.KEY_GAME_NAME, gameName); startActivity(intent); } @OnClick(R.id.language_dependence_root) @DebugLog public void onLanguageDependenceClick() { Bundle arguments = new Bundle(2); arguments.putInt(ActivityUtils.KEY_GAME_ID, Games.getGameId(gameUri)); arguments.putString(ActivityUtils.KEY_TYPE, POLL_TYPE_LANGUAGE_DEPENDENCE); DialogUtils.launchDialog(this, new PollFragment(), "poll-dialog", arguments); } @OnClick(R.id.comments_root) @DebugLog public void onCommentsClick() { Intent intent = new Intent(getActivity(), CommentsActivity.class); intent.setData(gameUri); intent.putExtra(ActivityUtils.KEY_GAME_NAME, gameName); startActivity(intent); } @DebugLog @OnClick(R.id.ratings_root) public void onRatingsClick() { Intent intent = new Intent(getActivity(), CommentsActivity.class); intent.setData(gameUri); intent.putExtra(ActivityUtils.KEY_GAME_NAME, gameName); intent.putExtra(ActivityUtils.KEY_SORT, CommentsActivity.SORT_RATING); startActivity(intent); } @DebugLog private void openOrCloseRanks() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { TransitionManager.beginDelayedTransition(subtypeContainer, expansionTransition); } subtypeContainer.setVisibility(isRanksExpanded ? View.VISIBLE : View.GONE); subtypeExpander.setImageResource(isRanksExpanded ? R.drawable.expander_close : R.drawable.expander_open); } @DebugLog private void openOrCloseDescription() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { TransitionManager.beginDelayedTransition(descriptionCard, expansionTransition); } descriptionView.setMaxLines(isDescriptionExpanded ? Integer.MAX_VALUE : 3); descriptionView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, isDescriptionExpanded ? R.drawable.expander_close : R.drawable.expander_open); } @SuppressWarnings("unused") @Subscribe public void onEvent(CollectionItemUpdatedEvent event) { SyncService.sync(getActivity(), SyncService.FLAG_SYNC_COLLECTION_UPLOAD); } @SuppressWarnings("unused") @DebugLog @OnClick({ R.id.link_bgg, R.id.link_bg_prices, R.id.link_amazon, R.id.link_amazon_uk, R.id.link_amazon_de, R.id.link_ebay }) void onLinkClick(View view) { switch (view.getId()) { case R.id.link_bgg: ActivityUtils.linkBgg(getActivity(), Games.getGameId(gameUri)); break; case R.id.link_bg_prices: ActivityUtils.linkBgPrices(getActivity(), gameName); break; case R.id.link_amazon: ActivityUtils.linkAmazon(getActivity(), gameName, ActivityUtils.LINK_AMAZON_COM); break; case R.id.link_amazon_uk: ActivityUtils.linkAmazon(getActivity(), gameName, ActivityUtils.LINK_AMAZON_UK); break; case R.id.link_amazon_de: ActivityUtils.linkAmazon(getActivity(), gameName, ActivityUtils.LINK_AMAZON_DE); break; case R.id.link_ebay: ActivityUtils.linkEbay(getActivity(), gameName); break; } } @OnClick({ R.id.number_of_players, R.id.player_age }) @DebugLog public void onPollClick(View view) { Bundle arguments = new Bundle(2); arguments.putInt(ActivityUtils.KEY_GAME_ID, Games.getGameId(gameUri)); arguments.putString(ActivityUtils.KEY_TYPE, (String) view.getTag()); DialogUtils.launchDialog(this, new PollFragment(), "poll-dialog", arguments); } @DebugLog public void triggerRefresh() { mightNeedRefreshing = false; int gameId = Games.getGameId(gameUri); UpdateService.start(getActivity(), UpdateService.SYNC_TYPE_GAME, gameId); UpdateService.start(getActivity(), UpdateService.SYNC_TYPE_GAME_COLLECTION, gameId); UpdateService.start(getActivity(), UpdateService.SYNC_TYPE_GAME_PLAYS, gameId); } private interface GameQuery { int _TOKEN = 0x11; String[] PROJECTION = { Games.GAME_ID, Games.STATS_AVERAGE, Games.YEAR_PUBLISHED, Games.MIN_PLAYERS, Games.MAX_PLAYERS, Games.PLAYING_TIME, Games.MINIMUM_AGE, Games.DESCRIPTION, Games.STATS_USERS_RATED, Games.UPDATED, Games.GAME_RANK, Games.GAME_NAME, Games.THUMBNAIL_URL, Games.STATS_BAYES_AVERAGE, Games.STATS_MEDIAN, Games.STATS_STANDARD_DEVIATION, Games.STATS_NUMBER_WEIGHTS, Games.STATS_AVERAGE_WEIGHT, Games.STATS_NUMBER_OWNED, Games.STATS_NUMBER_TRADING, Games.STATS_NUMBER_WANTING, Games.STATS_NUMBER_WISHING, Games.POLLS_COUNT, Games.IMAGE_URL, Games.SUBTYPE, Games.CUSTOM_PLAYER_SORT, Games.STATS_NUMBER_COMMENTS }; int GAME_ID = 0; int STATS_AVERAGE = 1; int YEAR_PUBLISHED = 2; int MIN_PLAYERS = 3; int MAX_PLAYERS = 4; int PLAYING_TIME = 5; int MINIMUM_AGE = 6; int DESCRIPTION = 7; int STATS_USERS_RATED = 8; int UPDATED = 9; int GAME_RANK = 10; int GAME_NAME = 11; int THUMBNAIL_URL = 12; int STATS_BAYES_AVERAGE = 13; int STATS_STANDARD_DEVIATION = 15; int STATS_NUMBER_WEIGHTS = 16; int STATS_AVERAGE_WEIGHT = 17; int STATS_NUMBER_OWNED = 18; int STATS_NUMBER_TRADING = 19; int STATS_NUMBER_WANTING = 20; int STATS_NUMBER_WISHING = 21; int POLLS_COUNT = 22; int IMAGE_URL = 23; int SUBTYPE = 24; int CUSTOM_PLAYER_SORT = 25; int STATS_NUMBER_COMMENTS = 26; } private interface DesignerQuery { int _TOKEN = 0x12; String[] PROJECTION = { Designers.DESIGNER_ID, Designers.DESIGNER_NAME, Designers._ID }; int DESIGNER_ID = 0; int DESIGNER_NAME = 1; } private interface ArtistQuery { int _TOKEN = 0x13; String[] PROJECTION = { Artists.ARTIST_ID, Artists.ARTIST_NAME, Artists._ID }; int ARTIST_ID = 0; int ARTIST_NAME = 1; } private interface PublisherQuery { int _TOKEN = 0x14; String[] PROJECTION = { Publishers.PUBLISHER_ID, Publishers.PUBLISHER_NAME, Publishers._ID }; int PUBLISHER_ID = 0; int PUBLISHER_NAME = 1; } private interface CategoryQuery { int _TOKEN = 0x15; String[] PROJECTION = { Categories.CATEGORY_ID, Categories.CATEGORY_NAME, Categories._ID }; int CATEGORY_ID = 0; int CATEGORY_NAME = 1; } private interface MechanicQuery { int _TOKEN = 0x16; String[] PROJECTION = { Mechanics.MECHANIC_ID, Mechanics.MECHANIC_NAME, Mechanics._ID }; int MECHANIC_ID = 0; int MECHANIC_NAME = 1; } private interface ExpansionQuery { int _TOKEN = 0x17; String[] PROJECTION = { GamesExpansions.EXPANSION_ID, GamesExpansions.EXPANSION_NAME, GamesExpansions._ID }; int EXPANSION_ID = 0; int EXPANSION_NAME = 1; } private interface BaseGameQuery { int _TOKEN = 0x18; String[] PROJECTION = { GamesExpansions.EXPANSION_ID, GamesExpansions.EXPANSION_NAME, GamesExpansions._ID }; int EXPANSION_ID = 0; int EXPANSION_NAME = 1; } private interface RankQuery { int _TOKEN = 0x19; String[] PROJECTION = { GameRanks.GAME_RANK_NAME, GameRanks.GAME_RANK_VALUE, GameRanks.GAME_RANK_TYPE, GameRanks.GAME_RANK_BAYES_AVERAGE }; int GAME_RANK_NAME = 0; int GAME_RANK_VALUE = 1; int GAME_RANK_TYPE = 2; int GAME_RANK_BAYES_AVERAGE = 3; } private interface CollectionQuery { String[] PROJECTION = { Collection._ID, Collection.COLLECTION_ID, Collection.COLLECTION_NAME, Collection.COLLECTION_YEAR_PUBLISHED, Collection.COLLECTION_THUMBNAIL_URL, Collection.STATUS_OWN, Collection.STATUS_PREVIOUSLY_OWNED, Collection.STATUS_FOR_TRADE, Collection.STATUS_WANT, Collection.STATUS_WANT_TO_BUY, Collection.STATUS_WISHLIST, Collection.STATUS_WANT_TO_PLAY, Collection.STATUS_PREORDERED, Collection.STATUS_WISHLIST_PRIORITY, Collection.NUM_PLAYS, Collection.COMMENT, Games.YEAR_PUBLISHED, Collection.RATING, Collection.IMAGE_URL }; int _TOKEN = 0x20; int _ID = 0; int COLLECTION_ID = 1; int COLLECTION_NAME = 2; int COLLECTION_YEAR = 3; int COLLECTION_THUMBNAIL_URL = 4; int STATUS_1 = 5; int STATUS_N = 12; int STATUS_WISHLIST = 10; int STATUS_WISHLIST_PRIORITY = 13; int NUM_PLAYS = 14; int COMMENT = 15; int YEAR_PUBLISHED = 16; int RATING = 17; int COLLECTION_IMAGE_URL = 18; } private interface PlaysQuery { String[] PROJECTION = { Plays._ID, Plays.MAX_DATE, Plays.SUM_QUANTITY, Plays.MAX_DATE }; int _TOKEN = 0x21; int MAX_DATE = 1; int SUM_QUANTITY = 2; } private interface ColorQuery { int _TOKEN = 0x22; } private interface LanguagePollQuery { String[] PROJECTION = { GamePollResultsResult.POLL_RESULTS_RESULT_VOTES, GamePollResultsResult.POLL_RESULTS_RESULT_LEVEL, GamePolls.POLL_TOTAL_VOTES }; int _TOKEN = 0x23; int POLL_RESULTS_RESULT_VOTES = 0; int POLL_RESULTS_RESULT_LEVEL = 1; int POLL_TOTAL_VOTES = 2; String SORT = GamePollResultsResult.POLL_RESULTS_SORT_INDEX + " ASC, " + GamePollResultsResult.POLL_RESULTS_RESULT_SORT_INDEX; } private class Game { final String Name; final String ThumbnailUrl; final String ImageUrl; final int Id; final double Rating; final int YearPublished; final int MinPlayers; final int MaxPlayers; final int PlayingTime; final int MinimumAge; final String Description; final int UsersRated; final int UsersCommented; final long Updated; final int Rank; final double BayesAverage; final double StandardDeviation; final double AverageWeight; final int NumberWeights; final int NumberOwned; final int NumberTrading; final int NumberWanting; final int NumberWishing; final int PollsCount; final String Subtype; final boolean CustomPlayerSort; public Game(Cursor cursor) { Name = cursor.getString(GameQuery.GAME_NAME); ThumbnailUrl = cursor.getString(GameQuery.THUMBNAIL_URL); ImageUrl = cursor.getString(GameQuery.IMAGE_URL); Id = cursor.getInt(GameQuery.GAME_ID); Rating = cursor.getDouble(GameQuery.STATS_AVERAGE); YearPublished = cursor.getInt(GameQuery.YEAR_PUBLISHED); MinPlayers = cursor.getInt(GameQuery.MIN_PLAYERS); MaxPlayers = cursor.getInt(GameQuery.MAX_PLAYERS); PlayingTime = cursor.getInt(GameQuery.PLAYING_TIME); MinimumAge = cursor.getInt(GameQuery.MINIMUM_AGE); Description = cursor.getString(GameQuery.DESCRIPTION); UsersRated = cursor.getInt(GameQuery.STATS_USERS_RATED); UsersCommented = cursor.getInt(GameQuery.STATS_NUMBER_COMMENTS); Updated = cursor.getLong(GameQuery.UPDATED); Rank = cursor.getInt(GameQuery.GAME_RANK); BayesAverage = cursor.getDouble(GameQuery.STATS_BAYES_AVERAGE); StandardDeviation = cursor.getDouble(GameQuery.STATS_STANDARD_DEVIATION); AverageWeight = cursor.getDouble(GameQuery.STATS_AVERAGE_WEIGHT); NumberWeights = cursor.getInt(GameQuery.STATS_NUMBER_WEIGHTS); NumberOwned = cursor.getInt(GameQuery.STATS_NUMBER_OWNED); NumberTrading = cursor.getInt(GameQuery.STATS_NUMBER_TRADING); NumberWanting = cursor.getInt(GameQuery.STATS_NUMBER_WANTING); NumberWishing = cursor.getInt(GameQuery.STATS_NUMBER_WISHING); PollsCount = cursor.getInt(GameQuery.POLLS_COUNT); Subtype = cursor.getString(GameQuery.SUBTYPE); CustomPlayerSort = (cursor.getInt(GameQuery.CUSTOM_PLAYER_SORT) == 1); } @DebugLog public String getAgeDescription() { if (MinimumAge > 0) { return MinimumAge + " " + getResources().getString(R.string.age_suffix); } return getResources().getString(R.string.text_unknown); } @DebugLog public int getMaxUsers() { int max = Math.max(UsersRated, UsersCommented); max = Math.max(max, NumberOwned); max = Math.max(max, NumberTrading); max = Math.max(max, NumberWanting); max = Math.max(max, NumberWeights); max = Math.max(max, NumberWishing); return max; } @DebugLog private String getPlayerRangeDescription() { if (MinPlayers == 0 && MaxPlayers == 0) { return getResources().getString(R.string.text_unknown); } else if (MinPlayers >= MaxPlayers) { return String.valueOf(MinPlayers); } else { return String.valueOf(MinPlayers) + " - " + String.valueOf(MaxPlayers); } } @DebugLog private String getPlayingTimeDescription() { if (PlayingTime > 0) { return PlayingTime + " " + getResources().getString(R.string.minutes_abbr); } return getResources().getString(R.string.text_unknown); } @DebugLog private String getRankDescription() { return PresentationUtils.describeRank(Rank); } @DebugLog public String getRatingDescription() { return PresentationUtils.describeRating(getContext(), Rating); } @DebugLog public String getYearPublished() { return PresentationUtils.describeYear(getContext(), YearPublished); } @DebugLog public String getSubtype() { return PresentationUtils.describeRankName(getActivity(), "subtype", Subtype); } } private class Rank { final String Name; final int Rank; final double Rating; final String Type; Rank(Cursor cursor) { Name = cursor.getString(RankQuery.GAME_RANK_NAME); Rank = cursor.getInt(RankQuery.GAME_RANK_VALUE); Rating = cursor.getDouble(RankQuery.GAME_RANK_BAYES_AVERAGE); Type = cursor.getString(RankQuery.GAME_RANK_TYPE); } } }