From 245b58124eeca6c6a72f072c10ad5d8dfb9e0d93 Mon Sep 17 00:00:00 2001 From: sigmabeta Date: Mon, 20 Jul 2015 22:46:12 -0400 Subject: [PATCH] Android TV: Add settings row, enabling access to other screens. --- .../activities/EmulationActivity.java | 4 +- .../dolphinemu/activities/MainActivity.java | 3 +- .../dolphinemu/activities/TvMainActivity.java | 117 +++++++++++++++--- ...mePresenter.java => GameRowPresenter.java} | 48 ++++--- .../adapters/SettingsRowPresenter.java | 47 +++++++ .../dolphinemu/model/TvSettingsItem.java | 31 +++++ .../viewholders/TvGameViewHolder.java | 7 +- .../viewholders/TvSettingsViewHolder.java | 23 ++++ .../app/src/main/res/drawable/ic_add_tv.png | Bin 0 -> 1708 bytes .../src/main/res/drawable/ic_refresh_tv.png | Bin 0 -> 1969 bytes .../src/main/res/drawable/ic_settings_tv.png | Bin 0 -> 2248 bytes 11 files changed, 232 insertions(+), 48 deletions(-) rename Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/{GamePresenter.java => GameRowPresenter.java} (81%) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvSettingsViewHolder.java create mode 100644 Source/Android/app/src/main/res/drawable/ic_add_tv.png create mode 100644 Source/Android/app/src/main/res/drawable/ic_refresh_tv.png create mode 100644 Source/Android/app/src/main/res/drawable/ic_settings_tv.png diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index c4cc9e1f3a..04b5149a0e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -50,8 +50,8 @@ public final class EmulationActivity extends AppCompatActivity private boolean mSystemUiVisible; private boolean mMenuVisible; - private static Interpolator sDecelerator = new DecelerateInterpolator(); - private static Interpolator sAccelerator = new AccelerateInterpolator(); + private static final Interpolator sDecelerator = new DecelerateInterpolator(); + private static final Interpolator sAccelerator = new AccelerateInterpolator(); /** * Handlers are a way to pass a message to an Activity telling it to do something diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java index a32b65d566..9ffa651cc2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java @@ -35,7 +35,7 @@ import org.dolphinemu.dolphinemu.services.AssetCopyService; */ public final class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks { - private static final int REQUEST_ADD_DIRECTORY = 1; + public static final int REQUEST_ADD_DIRECTORY = 1; public static final int REQUEST_EMULATE_GAME = 2; /** @@ -139,6 +139,7 @@ public final class MainActivity extends AppCompatActivity implements LoaderManag { fragment.refreshScreenshotAtPosition(resultCode); } + break; } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/TvMainActivity.java index 0e716d6503..1f58698a7e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/TvMainActivity.java @@ -17,12 +17,15 @@ import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; +import android.widget.Toast; import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.adapters.GamePresenter; +import org.dolphinemu.dolphinemu.adapters.GameRowPresenter; +import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter; import org.dolphinemu.dolphinemu.model.Game; import org.dolphinemu.dolphinemu.model.GameDatabase; import org.dolphinemu.dolphinemu.model.GameProvider; +import org.dolphinemu.dolphinemu.model.TvSettingsItem; import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; public final class TvMainActivity extends Activity @@ -56,24 +59,88 @@ public final class TvMainActivity extends Activity @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { - TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder; - // Start the emulation activity and send the path of the clicked ISO to it. - Intent intent = new Intent(TvMainActivity.this, EmulationActivity.class); + // Special case: user clicked on a settings row item. + if (item instanceof TvSettingsItem) + { + TvSettingsItem settingsItem = (TvSettingsItem) item; - intent.putExtra("SelectedGame", holder.path); - intent.putExtra("SelectedTitle", holder.title); - intent.putExtra("ScreenPath", holder.screenshotPath); + switch (settingsItem.getItemId()) + { + case R.id.menu_refresh: + getContentResolver().insert(GameProvider.URI_REFRESH, null); - ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation( - TvMainActivity.this, - holder.imageScreenshot, - "image_game_screenshot"); + // TODO Let the Activity know the data is refreshed in some other, better way. + recreate(); + break; - startActivity(intent, options.toBundle()); + case R.id.menu_settings: + // Launch the Settings Actvity. + Intent settings = new Intent(TvMainActivity.this, SettingsActivity.class); + startActivity(settings); + break; + + case R.id.button_add_directory: + Intent fileChooser = new Intent(TvMainActivity.this, AddDirectoryActivity.class); + + // The second argument to this method is read below in onActivityResult(). + startActivityForResult(fileChooser, MainActivity.REQUEST_ADD_DIRECTORY); + + break; + + default: + Toast.makeText(TvMainActivity.this, "Unimplemented menu option.", Toast.LENGTH_SHORT).show(); + break; + } + } + else + { + TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder; + // Start the emulation activity and send the path of the clicked ISO to it. + Intent intent = new Intent(TvMainActivity.this, EmulationActivity.class); + + intent.putExtra("SelectedGame", holder.path); + intent.putExtra("SelectedTitle", holder.title); + intent.putExtra("ScreenPath", holder.screenshotPath); + + ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation( + TvMainActivity.this, + holder.imageScreenshot, + "image_game_screenshot"); + + startActivity(intent, options.toBundle()); + } } }); } + /** + * Callback from AddDirectoryActivity. Applies any changes necessary to the GameGridActivity. + * + * @param requestCode An int describing whether the Activity that is returning did so successfully. + * @param resultCode An int describing what Activity is giving us this callback. + * @param result The information the returning Activity is providing us. + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent result) + { + switch (requestCode) + { + case MainActivity.REQUEST_ADD_DIRECTORY: + // If the user picked a file, as opposed to just backing out. + if (resultCode == RESULT_OK) + { + // Sanity check to make sure the Activity that just returned was the AddDirectoryActivity; + // other activities might use this callback in the future (don't forget to change Javadoc!) + if (requestCode == MainActivity.REQUEST_ADD_DIRECTORY) + { + // TODO Let the Activity know the data is refreshed in some other, better way. + recreate(); + } + } + break; + } + } + private void buildRowsAdapter() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); @@ -90,13 +157,16 @@ public final class TvMainActivity extends Activity } } + ListRow settingsRow = buildSettingsRow(); + mRowsAdapter.add(settingsRow); + mBrowseFragment.setAdapter(mRowsAdapter); } private ListRow buildGamesRow(int platform) { // Create an adapter for this row. - CursorObjectAdapter row = new CursorObjectAdapter(new GamePresenter(platform)); + CursorObjectAdapter row = new CursorObjectAdapter(new GameRowPresenter()); Cursor games; if (platform == Game.PLATFORM_ALL) @@ -175,8 +245,25 @@ public final class TvMainActivity extends Activity return new ListRow(header, row); } - /*private ListRow buildSettingsRow() + private ListRow buildSettingsRow() { + ArrayObjectAdapter rowItems = new ArrayObjectAdapter(new SettingsRowPresenter()); - }*/ + rowItems.add(new TvSettingsItem(R.id.menu_refresh, + R.drawable.ic_refresh_tv, + R.string.grid_menu_refresh)); + + rowItems.add(new TvSettingsItem(R.id.menu_settings, + R.drawable.ic_settings_tv, + R.string.grid_menu_settings)); + + rowItems.add(new TvSettingsItem(R.id.button_add_directory, + R.drawable.ic_add_tv, + R.string.add_directory_title)); + + // Create a header for this row. + HeaderItem header = new HeaderItem(R.string.settings, getString(R.string.settings)); + + return new ListRow(header, rowItems); + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GamePresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java similarity index 81% rename from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GamePresenter.java rename to Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java index d9b4487278..3b0b8095ce 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GamePresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java @@ -16,15 +16,8 @@ import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; * The Leanback library / docs call this a Presenter, but it works very * similarly to a RecyclerView.ViewHolder. */ -public final class GamePresenter extends Presenter +public final class GameRowPresenter extends Presenter { - private int mPlatform; - - public GamePresenter(int platform) - { - mPlatform = platform; - } - public ViewHolder onCreateViewHolder(ViewGroup parent) { // Create a new view. @@ -80,6 +73,25 @@ public final class GamePresenter extends Presenter holder.country = game.getCountry(); holder.company = game.getCompany(); holder.screenshotPath = game.getScreenshotPath(); + + switch (game.getPlatform()) + { + case Game.PLATFORM_GC: + holder.cardParent.setTag(R.color.dolphin_accent_gamecube); + break; + + case Game.PLATFORM_WII: + holder.cardParent.setTag(R.color.dolphin_accent_wii); + break; + + case Game.PLATFORM_WII_WARE: + holder.cardParent.setTag(R.color.dolphin_accent_wiiware); + break; + + default: + holder.cardParent.setTag(android.R.color.holo_red_dark); + break; + } } public void onUnbindViewHolder(ViewHolder viewHolder) @@ -93,24 +105,8 @@ public final class GamePresenter extends Presenter if (selected) { - switch (mPlatform) - { - case Game.PLATFORM_GC: - backgroundColor = R.color.dolphin_accent_gamecube; - break; - - case Game.PLATFORM_WII: - backgroundColor = R.color.dolphin_accent_wii; - break; - - case Game.PLATFORM_WII_WARE: - backgroundColor = R.color.dolphin_accent_wiiware; - break; - - default: - backgroundColor = android.R.color.holo_red_dark; - break; - } + // TODO: 7/20/15 Try using view tag to set color + backgroundColor = (int) view.getTag(); } else { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.java new file mode 100644 index 0000000000..beef06a218 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/SettingsRowPresenter.java @@ -0,0 +1,47 @@ +package org.dolphinemu.dolphinemu.adapters; + + +import android.content.res.Resources; +import android.support.v17.leanback.widget.ImageCardView; +import android.support.v17.leanback.widget.Presenter; +import android.view.ViewGroup; + +import org.dolphinemu.dolphinemu.model.TvSettingsItem; +import org.dolphinemu.dolphinemu.viewholders.TvSettingsViewHolder; + +public final class SettingsRowPresenter extends Presenter +{ + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) + { + // Create a new view. + ImageCardView settingsCard = new ImageCardView(parent.getContext()); + + settingsCard.setMainImageAdjustViewBounds(true); + settingsCard.setMainImageDimensions(192, 160); + + + settingsCard.setFocusable(true); + settingsCard.setFocusableInTouchMode(true); + + // Use that view to create a ViewHolder. + return new TvSettingsViewHolder(settingsCard); + } + + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) + { + TvSettingsViewHolder holder = (TvSettingsViewHolder) viewHolder; + TvSettingsItem settingsItem = (TvSettingsItem) item; + + Resources resources = holder.cardParent.getResources(); + + holder.itemId = settingsItem.getItemId(); + + holder.cardParent.setTitleText(resources.getString(settingsItem.getLabelId())); + holder.cardParent.setMainImage(resources.getDrawable(settingsItem.getIconId(), null)); + } + + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) + { + // no op + } +} \ No newline at end of file diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java new file mode 100644 index 0000000000..ccd87bfa4c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java @@ -0,0 +1,31 @@ +package org.dolphinemu.dolphinemu.model; + + +public final class TvSettingsItem +{ + private final int mItemId; + private final int mIconId; + private final int mLabelId; + + public TvSettingsItem(int itemId, int iconId, int labelId) + { + mItemId = itemId; + mIconId = iconId; + mLabelId = labelId; + } + + public int getItemId() + { + return mItemId; + } + + public int getIconId() + { + return mIconId; + } + + public int getLabelId() + { + return mLabelId; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java index dd996ccbe7..d27a671c2f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java @@ -4,19 +4,16 @@ import android.support.v17.leanback.widget.ImageCardView; import android.support.v17.leanback.widget.Presenter; import android.view.View; import android.widget.ImageView; -import android.widget.TextView; /** * A simple class that stores references to views so that the GameAdapter doesn't need to * keep calling findViewById(), which is expensive. */ -public class TvGameViewHolder extends Presenter.ViewHolder +public final class TvGameViewHolder extends Presenter.ViewHolder { public ImageCardView cardParent; public ImageView imageScreenshot; - public TextView textGameTitle; - public TextView textCompany; public String gameId; @@ -28,6 +25,8 @@ public class TvGameViewHolder extends Presenter.ViewHolder public String company; public String screenshotPath; + public int backgroundColor; + public TvGameViewHolder(View itemView) { super(itemView); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvSettingsViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvSettingsViewHolder.java new file mode 100644 index 0000000000..3264e93f5b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvSettingsViewHolder.java @@ -0,0 +1,23 @@ +package org.dolphinemu.dolphinemu.viewholders; + + +import android.support.v17.leanback.widget.ImageCardView; +import android.support.v17.leanback.widget.Presenter; +import android.view.View; + +public final class TvSettingsViewHolder extends Presenter.ViewHolder +{ + public ImageCardView cardParent; + + // Determines what action to take when this item is clicked. + public int itemId; + + public TvSettingsViewHolder(View itemView) + { + super(itemView); + + itemView.setTag(this); + + cardParent = (ImageCardView) itemView; + } +} diff --git a/Source/Android/app/src/main/res/drawable/ic_add_tv.png b/Source/Android/app/src/main/res/drawable/ic_add_tv.png new file mode 100644 index 0000000000000000000000000000000000000000..7332c7572767695645983c7c261daef6cb088fb6 GIT binary patch literal 1708 zcmZvddpMMN8^?doW5!^aK~C!|L#BqTq?tq%<4}%6O|(44+p_42TCSn5!R_4!tOi$^ZxbzaeqI5-1q&xuKT*L`?|0A`+BIU z=&Jw#)Vw_10_DU$hZ0V%YtO4V04OGTxw!_VLG!PBi1xxkxj==ez3^nAMXdFW|jTP-&#d(=G!Ig;JzlyJD4mMDu4yB`Wl zX`)o}b2xr5^d!1teOGev195m-TN?07xMu&6l%hjGiu^poArofbK}^v%U`PFPT9Mzi8$0_%Kh{q48o8Uw)U;_$v6*G7uD+bj2QAjBSR5 zUs`;)Q@yJel&^)j&T#2qeA7wI1!B_BKYi+hrS7(4PUTe_ARFvP~%G#~#uts7J{m5$>B}i1yp) z&^t0{Zur;eklp)MX#cJ965%0A7^=X}$guWB)p{xy=0jhI35&!ob}jYs1p{5oG#PqR zqunf4SX5S$wttyw;#3OZ(n&jhi^BBx(Ve$Sadd zo)-0I_dpP>Dg5Sgm%}8{)(Ye_&vw+2Yx`%2NTxMfGmv*=$X zTusKoN&!FKU59M%|%&AOmljeev zx^Z2kRE{}a?3NC?p)rI<+(2B=5=nj|4znvHPIJfZqJ`!J=Zo>Il~2ta#c1hRH$yRh{8W7I>u1yFWwYh$gnC;J)BQjX`bN ztN1?4*C!!P*p$`jhu?M%0A%iTNyVd1CMyJRPQiIWm@(A{FtWFQwB$K}O7DPAYCHy* zuy>y}qSgbW%`0uGyukAiN3*6YsmAJ5Kiv99YuQ#8tfvhxAHzyn$K)=&F6NOrQ6o-n zUC?IV&1Ag*oUnE>$!A%Q=MF0KA4WcrO?MV6B#ec%@2It2-hP>FfK5kSq&oCjQb|Cp zPkeZk7cy>Ihzt{=&!UNq1ME**P!}Uq8q+pE;(-Ch&$hk+P(MJ$`|`Otv|1k~8`8 z*CQt_-@?wnqhJZn6!?JugZaN_;?ffv$u1w{UkG@)`?@_igk1bD`E%~q literal 0 HcmV?d00001 diff --git a/Source/Android/app/src/main/res/drawable/ic_refresh_tv.png b/Source/Android/app/src/main/res/drawable/ic_refresh_tv.png new file mode 100644 index 0000000000000000000000000000000000000000..8bae5d34ab837c7e7d44a77008f3e956a729bff7 GIT binary patch literal 1969 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 W#h1@&<)CH&0000