Android: Update mobile and TV to use game covers

Using covers should give a consistent look to dolphin's library.
This commit is contained in:
zackhow 2018-08-05 16:11:33 -04:00
parent 84c24516b1
commit 3f21975d2a
17 changed files with 223 additions and 111 deletions

View File

@ -37,7 +37,7 @@ public final class GameRowPresenter extends Presenter
ImageCardView gameCard = new ImageCardView(parent.getContext());
gameCard.setMainImageAdjustViewBounds(true);
gameCard.setMainImageDimensions(480, 320);
gameCard.setMainImageDimensions(240, 336);
gameCard.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP);
gameCard.setFocusable(true);

View File

@ -199,6 +199,7 @@ public final class SettingsFragmentPresenter
sl.add(new SubmenuSetting(null, null, R.string.gamecube_submenu, 0, MenuTag.CONFIG_GAME_CUBE));
sl.add(new SubmenuSetting(null, null, R.string.wii_submenu, 0, MenuTag.CONFIG_WII));
sl.add(new HeaderSetting(null, null, R.string.gametdb_thanks, 0));
}
private void addGeneralSettings(ArrayList<SettingsItem> sl)

View File

@ -1,7 +1,11 @@
package org.dolphinemu.dolphinemu.model;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import org.dolphinemu.dolphinemu.utils.CoverHelper;
public class GameFile
{
private long mPointer; // Do not rename or move without editing the native code
@ -19,12 +23,24 @@ public class GameFile
public native String getDescription();
public native String getCompany();
public native int getCountry();
public native int getRegion();
public native String getPath();
public native String getGameId();
public native int[] getBanner();
public native int getBannerWidth();
public native int getBannerHeight();
public String getCoverPath()
{
return Environment.getExternalStorageDirectory().getPath() +
"/dolphin-emu/Cache/GameCovers/" + getGameId() + ".png";
}
public String getCustomCoverPath()
{
return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png";
}
public String getScreenshotPath()
{
String gameId = getGameId();

View File

@ -12,8 +12,6 @@ import org.dolphinemu.dolphinemu.ui.platform.Platform;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

View File

@ -154,6 +154,7 @@ public class SyncProgramsJobService extends JobService
.setTitle(game.getTitle())
.setDescription(game.getDescription())
.setPosterArtUri(banner)
.setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3)
.setIntentUri(appLinkUri);
return builder.build();
}

View File

@ -2,7 +2,6 @@ package org.dolphinemu.dolphinemu.ui.main;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v17.leanback.app.BrowseFragment;
import android.support.v17.leanback.app.BrowseSupportFragment;
@ -197,8 +196,6 @@ public final class TvMainActivity extends FragmentActivity implements MainView
GameFileCacheService.startLoad(this);
}
mRowsAdapter.add(buildSettingsRow());
for (Platform platform : Platform.values())
{
ListRow row = buildGamesRow(platform, GameFileCacheService.getGameFilesForPlatform(platform));
@ -210,6 +207,8 @@ public final class TvMainActivity extends FragmentActivity implements MainView
}
}
mRowsAdapter.add(buildSettingsRow());
mBrowseFragment.setAdapter(mRowsAdapter);
}

View File

@ -0,0 +1,81 @@
package org.dolphinemu.dolphinemu.utils;
import android.graphics.Bitmap;
import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import java.io.FileOutputStream;
public final class CoverHelper
{
private static String baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png";
public static String buildGameTDBUrl(GameFile game, String region)
{
String gameId = game.getGameId();
if(game.getPlatform() == 2) // WiiWare
gameId = gameId.substring(0,4);
return String.format(baseUrl, region, gameId);
}
public static String getRegion(GameFile game)
{
String region;
switch(game.getRegion())
{
case 0: // NTSC_J
region = "JA";
break;
case 1: // NTSC_U
region = "US";
break;
case 4: // NTSC_K
region = "KO";
break;
case 2: // PAL
switch (game.getCountry())
{
case 2: // German
region = "DE";
break;
case 3: // French
region = "FR";
break;
case 4: // Spanish
region = "ES";
break;
case 5: // Italian
region = "IT";
break;
case 6: // Dutch
region = "NL";
break;
case 1: // English
default:
region = "EN";
break;
}
break;
case 3: // Unknown
default:
region = "EN";
break;
}
return region;
}
public static void saveCover(Bitmap cover, String path)
{
try
{
FileOutputStream out = new FileOutputStream(path);
cover.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
}
catch (Exception e)
{
// Do nothing
}
}
}

View File

@ -1,36 +0,0 @@
package org.dolphinemu.dolphinemu.utils;
import android.graphics.Bitmap;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Request;
import com.squareup.picasso.RequestHandler;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.model.GameFile;
import java.io.IOException;
import java.nio.IntBuffer;
public class GameBannerRequestHandler extends RequestHandler {
GameFile mGameFile;
public GameBannerRequestHandler(GameFile gameFile)
{
mGameFile = gameFile;
}
@Override
public boolean canHandleRequest(Request data) {
return true;
}
@Override
public Result load(Request request, int networkPolicy) {
int[] vector = mGameFile.getBanner();
int width = mGameFile.getBannerWidth();
int height = mGameFile.getBannerHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(vector, 0, width, 0, 0, width, height);
return new Result(bitmap, Picasso.LoadedFrom.DISK);
}
}

View File

@ -1,46 +1,114 @@
package org.dolphinemu.dolphinemu.utils;
import android.graphics.Bitmap;
import android.net.Uri;
import android.graphics.drawable.BitmapDrawable;
import android.widget.ImageView;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.model.GameFile;
import java.io.File;
import java.net.URI;
public class PicassoUtils {
public static void loadGameBanner(ImageView imageView, GameFile gameFile) {
File screenshotFile = new File(URI.create(gameFile.getScreenshotPath()));
if (screenshotFile.exists()) {
// Fill in the view contents.
public static void loadGameBanner(ImageView imageView, GameFile gameFile)
{
File cover = new File(gameFile.getCustomCoverPath());
if (cover.exists())
{
Picasso.with(imageView.getContext())
.load(gameFile.getScreenshotPath())
.fit()
.centerCrop()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.RGB_565)
.error(R.drawable.no_banner)
.into(imageView);
} else {
Picasso picassoInstance = new Picasso.Builder(imageView.getContext())
.addRequestHandler(new GameBannerRequestHandler(gameFile))
.build();
picassoInstance
.load(Uri.parse("iso:/" + gameFile.getPath()))
.fit()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.RGB_565)
.error(R.drawable.no_banner)
.into(imageView);
.load(cover)
.fit()
.centerCrop()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.ARGB_8888)
.error(R.drawable.no_banner)
.into(imageView);
}
else if ((cover = new File(gameFile.getCoverPath())).exists())
{
Picasso.with(imageView.getContext())
.load(cover)
.fit()
.centerCrop()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.ARGB_8888)
.error(R.drawable.no_banner)
.into(imageView);
}
/**
* GameTDB has a pretty close to complete collection for US/EN covers. First pass at getting
* the cover will be by the disk's region, second will be the US cover, and third EN.
*/
else
{
Picasso.with(imageView.getContext())
.load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile)))
.fit()
.centerCrop()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.ARGB_8888)
.error(R.drawable.no_banner)
.into(imageView, new Callback()
{
@Override
public void onSuccess()
{
CoverHelper.saveCover(((BitmapDrawable) imageView.getDrawable()).getBitmap(),
gameFile.getCoverPath());
}
@Override
public void onError() // Second pass using US region
{
Picasso.with(imageView.getContext())
.load(CoverHelper.buildGameTDBUrl(gameFile, "US"))
.fit()
.centerCrop()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.ARGB_8888)
.error(R.drawable.no_banner)
.into(imageView, new Callback()
{
@Override
public void onSuccess()
{
CoverHelper.saveCover(((BitmapDrawable) imageView.getDrawable()).getBitmap(),
gameFile.getCoverPath());
}
@Override
public void onError() // Third and last pass using EN region
{
Picasso.with(imageView.getContext())
.load(CoverHelper.buildGameTDBUrl(gameFile, "EN"))
.fit()
.centerCrop()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.ARGB_8888)
.error(R.drawable.no_banner)
.into(imageView, new Callback()
{
@Override
public void onSuccess()
{
CoverHelper.saveCover(((BitmapDrawable) imageView.getDrawable()).getBitmap(),
gameFile.getCoverPath());
}
@Override
public void onError()
{
}
});
}
});
}
});
}
}
}

View File

@ -154,8 +154,8 @@ public class TvUtil
}
/**
* Leanback lanucher requires a uri for poster art, so we take the banner vector,
* make a bitmap, save that bitmap, then return the file provider uri.
* Leanback lanucher requires a uri for poster art so we create a contentUri and
* pass that to LEANBACK_PACKAGE
*/
public static Uri buildBanner(GameFile game, Context context)
{
@ -163,33 +163,14 @@ public class TvUtil
try
{
//Substring needed to strip "file:" from the path beginning
File screenshotFile = new File(game.getScreenshotPath().substring(5));
if (screenshotFile.exists())
File cover = new File(game.getCustomCoverPath());
if(cover.exists())
{
contentUri = getUriForFile(context, getFilePrivider(context), screenshotFile);
contentUri = getUriForFile(context, getFileProvider(context), cover);
}
else
else if ((cover = new File(game.getCoverPath())).exists())
{
File file = new File(buildBannerFilename(game.getGameId()));
if (!file.exists())
{
int[] vector = game.getBanner();
int width = game.getBannerWidth();
int height = game.getBannerHeight();
if (vector.length > 0 || width > 0 || height > 0)
{
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(vector, 0, width, 0, 0, width, height);
FileOutputStream out = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
}
else
return null;
}
contentUri = getUriForFile(context, getFilePrivider(context), file);
contentUri = getUriForFile(context, getFileProvider(context), cover);
}
context.grantUriPermission(LEANBACK_PACKAGE, contentUri,
FLAG_GRANT_READ_URI_PERMISSION);
@ -203,16 +184,10 @@ public class TvUtil
return contentUri;
}
private static String buildBannerFilename(String gameId)
{
return Environment.getExternalStorageDirectory().getPath() +
"/dolphin-emu/Cache/" + gameId + "_banner.png";
}
/**
* Needed since debug builds append '.debug' to the end of the package
*/
private static String getFilePrivider(Context context)
private static String getFileProvider(Context context)
{
return context.getPackageName() + ".filesprovider";
}

View File

@ -2,8 +2,8 @@
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
tools:layout_width="224dp"
android:layout_height="256dp"
tools:layout_width="160dp"
android:layout_height="368dp"
android:transitionName="card_game"
android:focusable="true"
android:clickable="true"

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="game_grid_columns">4</integer>
<integer name="game_grid_columns">6</integer>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="game_grid_columns">2</integer>
<integer name="game_grid_columns">3</integer>
</resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="game_grid_columns">3</integer>
<integer name="game_grid_columns">4</integer>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="game_grid_columns">1</integer>
<integer name="game_grid_columns">2</integer>
<!-- Default GameCube landscape layout -->
<integer name="BUTTON_A_X">865</integer>
@ -81,4 +81,4 @@
<integer name="CLASSIC_TRIGGER_L_Y">429</integer>
<integer name="CLASSIC_TRIGGER_R_X">737</integer>
<integer name="CLASSIC_TRIGGER_R_Y">311</integer>
</resources>
</resources>

View File

@ -135,6 +135,7 @@
<string name="wiimote_speaker_description">Enable sound output through the speaker on a real Wiimote (DolphinBar required).</string>
<string name="audio_stretch">Audio Stretching</string>
<string name="audio_stretch_description">Stretches audio to reduce stuttering. Increases latency.</string>
<string name="gametdb_thanks">Thanks to GameTDB.com for providing GameCube and Wii covers!</string>
<!-- Interface Preference Fragment -->
<string name="interface_submenu">Interface</string>

View File

@ -52,6 +52,8 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCompa
jobject obj);
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCountry(JNIEnv* env,
jobject obj);
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRegion(JNIEnv* env,
jobject obj);
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath(JNIEnv* env,
jobject obj);
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getGameId(JNIEnv* env,
@ -99,6 +101,12 @@ JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCountry(
return static_cast<jint>(GetRef(env, obj)->GetCountry());
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getRegion(JNIEnv* env,
jobject obj)
{
return static_cast<jint>(GetRef(env, obj)->GetRegion());
}
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath(JNIEnv* env,
jobject obj)
{