diff options
| author | 2023-02-12 00:17:19 -0800 | |
|---|---|---|
| committer | 2023-06-03 00:05:30 -0700 | |
| commit | 0e52d11ede15d0a47761dce42ebf1a8f7eb83719 (patch) | |
| tree | 5ce614dc7aaa5c7251733f10dcda141baf33ae4b /src/android | |
| parent | android: Replace notification icon with yuzu (diff) | |
| download | yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.gz yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.tar.xz yuzu-0e52d11ede15d0a47761dce42ebf1a8f7eb83719.zip | |
android: frontend: Implement game grid view. (#9)
Diffstat (limited to 'src/android')
15 files changed, 272 insertions, 174 deletions
diff --git a/src/android/app/build.gradle b/src/android/app/build.gradle index c516b2bff..74835113c 100644 --- a/src/android/app/build.gradle +++ b/src/android/app/build.gradle | |||
| @@ -11,7 +11,7 @@ def abiFilter = "arm64-v8a" //, "x86" | |||
| 11 | 11 | ||
| 12 | android { | 12 | android { |
| 13 | compileSdkVersion 32 | 13 | compileSdkVersion 32 |
| 14 | ndkVersion "25.1.8937393" | 14 | ndkVersion "25.2.9519653" |
| 15 | 15 | ||
| 16 | compileOptions { | 16 | compileOptions { |
| 17 | sourceCompatibility JavaVersion.VERSION_1_8 | 17 | sourceCompatibility JavaVersion.VERSION_1_8 |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java index 75395bd4c..7a1ddd38e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java | |||
| @@ -141,9 +141,9 @@ public final class NativeLibrary { | |||
| 141 | * Gets the embedded icon within the given ROM. | 141 | * Gets the embedded icon within the given ROM. |
| 142 | * | 142 | * |
| 143 | * @param filename the file path to the ROM. | 143 | * @param filename the file path to the ROM. |
| 144 | * @return an integer array containing the color data for the icon. | 144 | * @return a byte array containing the JPEG data for the icon. |
| 145 | */ | 145 | */ |
| 146 | public static native int[] GetIcon(String filename); | 146 | public static native byte[] GetIcon(String filename); |
| 147 | 147 | ||
| 148 | /** | 148 | /** |
| 149 | * Gets the embedded title of the given ISO/ROM. | 149 | * Gets the embedded title of the given ISO/ROM. |
| @@ -205,6 +205,11 @@ public final class NativeLibrary { | |||
| 205 | public static native void StopEmulation(); | 205 | public static native void StopEmulation(); |
| 206 | 206 | ||
| 207 | /** | 207 | /** |
| 208 | * Resets the in-memory ROM metadata cache. | ||
| 209 | */ | ||
| 210 | public static native void ResetRomMetadata(); | ||
| 211 | |||
| 212 | /** | ||
| 208 | * Returns true if emulation is running (or is paused). | 213 | * Returns true if emulation is running (or is paused). |
| 209 | */ | 214 | */ |
| 210 | public static native boolean IsRunning(); | 215 | public static native boolean IsRunning(); |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java index cd9f823d4..ed1a000c7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.java | |||
| @@ -86,11 +86,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl | |||
| 86 | mCursor.getString(GameDatabase.GAME_COLUMN_PATH)); | 86 | mCursor.getString(GameDatabase.GAME_COLUMN_PATH)); |
| 87 | 87 | ||
| 88 | holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " ")); | 88 | holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " ")); |
| 89 | holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY)); | 89 | holder.textGameCaption.setText(mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION)); |
| 90 | |||
| 91 | String filepath = mCursor.getString(GameDatabase.GAME_COLUMN_PATH); | ||
| 92 | String filename = FileUtil.getFilename(YuzuApplication.getAppContext(), filepath); | ||
| 93 | holder.textFileName.setText(filename); | ||
| 94 | 90 | ||
| 95 | // TODO These shouldn't be necessary once the move to a DB-based model is complete. | 91 | // TODO These shouldn't be necessary once the move to a DB-based model is complete. |
| 96 | holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID); | 92 | holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID); |
| @@ -98,7 +94,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl | |||
| 98 | holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE); | 94 | holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE); |
| 99 | holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION); | 95 | holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION); |
| 100 | holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS); | 96 | holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS); |
| 101 | holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY); | 97 | holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION); |
| 102 | 98 | ||
| 103 | final int backgroundColorId = isValidGame(holder.path) ? R.color.view_background : R.color.view_disabled; | 99 | final int backgroundColorId = isValidGame(holder.path) ? R.color.view_background : R.color.view_disabled; |
| 104 | View itemView = holder.getItemView(); | 100 | View itemView = holder.getItemView(); |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java index bc1b19bd1..681117268 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.java | |||
| @@ -47,7 +47,7 @@ public final class Game { | |||
| 47 | cursor.getString(GameDatabase.GAME_COLUMN_REGIONS), | 47 | cursor.getString(GameDatabase.GAME_COLUMN_REGIONS), |
| 48 | cursor.getString(GameDatabase.GAME_COLUMN_PATH), | 48 | cursor.getString(GameDatabase.GAME_COLUMN_PATH), |
| 49 | cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID), | 49 | cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID), |
| 50 | cursor.getString(GameDatabase.GAME_COLUMN_COMPANY)); | 50 | cursor.getString(GameDatabase.GAME_COLUMN_CAPTION)); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | public String getTitle() { | 53 | public String getTitle() { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java index 771e35c69..a10ac6ff2 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java | |||
| @@ -29,7 +29,7 @@ public final class GameDatabase extends SQLiteOpenHelper { | |||
| 29 | public static final int GAME_COLUMN_DESCRIPTION = 3; | 29 | public static final int GAME_COLUMN_DESCRIPTION = 3; |
| 30 | public static final int GAME_COLUMN_REGIONS = 4; | 30 | public static final int GAME_COLUMN_REGIONS = 4; |
| 31 | public static final int GAME_COLUMN_GAME_ID = 5; | 31 | public static final int GAME_COLUMN_GAME_ID = 5; |
| 32 | public static final int GAME_COLUMN_COMPANY = 6; | 32 | public static final int GAME_COLUMN_CAPTION = 6; |
| 33 | public static final int FOLDER_COLUMN_PATH = 1; | 33 | public static final int FOLDER_COLUMN_PATH = 1; |
| 34 | public static final String KEY_DB_ID = "_id"; | 34 | public static final String KEY_DB_ID = "_id"; |
| 35 | public static final String KEY_GAME_PATH = "path"; | 35 | public static final String KEY_GAME_PATH = "path"; |
| @@ -176,6 +176,9 @@ public final class GameDatabase extends SQLiteOpenHelper { | |||
| 176 | return; | 176 | return; |
| 177 | } | 177 | } |
| 178 | 178 | ||
| 179 | // Ensure keys are loaded so that ROM metadata can be decrypted. | ||
| 180 | NativeLibrary.ReloadKeys(); | ||
| 181 | |||
| 179 | MinimalDocumentFile[] children = FileUtil.listFiles(context, parent); | 182 | MinimalDocumentFile[] children = FileUtil.listFiles(context, parent); |
| 180 | for (MinimalDocumentFile file : children) { | 183 | for (MinimalDocumentFile file : children) { |
| 181 | if (file.isDirectory()) { | 184 | if (file.isDirectory()) { |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java index 7fdd692c2..552232bd3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.java | |||
| @@ -161,6 +161,7 @@ public final class MainActivity extends AppCompatActivity implements MainView { | |||
| 161 | if (FileUtil.copyUriToInternalStorage(this, result.getData(), dstPath, "prod.keys")) { | 161 | if (FileUtil.copyUriToInternalStorage(this, result.getData(), dstPath, "prod.keys")) { |
| 162 | if (NativeLibrary.ReloadKeys()) { | 162 | if (NativeLibrary.ReloadKeys()) { |
| 163 | Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show(); | 163 | Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show(); |
| 164 | refreshFragment(); | ||
| 164 | } else { | 165 | } else { |
| 165 | Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show(); | 166 | Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show(); |
| 166 | launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); | 167 | launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS); |
| @@ -184,6 +185,7 @@ public final class MainActivity extends AppCompatActivity implements MainView { | |||
| 184 | 185 | ||
| 185 | private void refreshFragment() { | 186 | private void refreshFragment() { |
| 186 | if (mPlatformGamesFragment != null) { | 187 | if (mPlatformGamesFragment != null) { |
| 188 | NativeLibrary.ResetRomMetadata(); | ||
| 187 | mPlatformGamesFragment.refresh(); | 189 | mPlatformGamesFragment.refresh(); |
| 188 | } | 190 | } |
| 189 | } | 191 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java index 6c327b1b8..2d74f43ca 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/platform/PlatformGamesFragment.java | |||
| @@ -5,6 +5,7 @@ import android.os.Bundle; | |||
| 5 | import android.view.LayoutInflater; | 5 | import android.view.LayoutInflater; |
| 6 | import android.view.View; | 6 | import android.view.View; |
| 7 | import android.view.ViewGroup; | 7 | import android.view.ViewGroup; |
| 8 | import android.view.ViewTreeObserver; | ||
| 8 | import android.widget.TextView; | 9 | import android.widget.TextView; |
| 9 | 10 | ||
| 10 | import androidx.core.content.ContextCompat; | 11 | import androidx.core.content.ContextCompat; |
| @@ -13,6 +14,7 @@ import androidx.recyclerview.widget.GridLayoutManager; | |||
| 13 | import androidx.recyclerview.widget.RecyclerView; | 14 | import androidx.recyclerview.widget.RecyclerView; |
| 14 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | 15 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; |
| 15 | 16 | ||
| 17 | import org.yuzu.yuzu_emu.NativeLibrary; | ||
| 16 | import org.yuzu.yuzu_emu.YuzuApplication; | 18 | import org.yuzu.yuzu_emu.YuzuApplication; |
| 17 | import org.yuzu.yuzu_emu.R; | 19 | import org.yuzu.yuzu_emu.R; |
| 18 | import org.yuzu.yuzu_emu.adapters.GameAdapter; | 20 | import org.yuzu.yuzu_emu.adapters.GameAdapter; |
| @@ -43,19 +45,34 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam | |||
| 43 | 45 | ||
| 44 | @Override | 46 | @Override |
| 45 | public void onViewCreated(View view, Bundle savedInstanceState) { | 47 | public void onViewCreated(View view, Bundle savedInstanceState) { |
| 46 | int columns = getResources().getInteger(R.integer.game_grid_columns); | ||
| 47 | RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns); | ||
| 48 | mAdapter = new GameAdapter(); | 48 | mAdapter = new GameAdapter(); |
| 49 | 49 | ||
| 50 | mRecyclerView.setLayoutManager(layoutManager); | 50 | // Organize our grid layout based on the current view. |
| 51 | mRecyclerView.setAdapter(mAdapter); | 51 | if (isAdded()) { |
| 52 | mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(ContextCompat.getDrawable(getActivity(), R.drawable.gamelist_divider), 1)); | 52 | view.getViewTreeObserver() |
| 53 | .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { | ||
| 54 | @Override | ||
| 55 | public void onGlobalLayout() { | ||
| 56 | if (view.getMeasuredWidth() == 0) { | ||
| 57 | return; | ||
| 58 | } | ||
| 59 | |||
| 60 | int columns = view.getMeasuredWidth() / | ||
| 61 | requireContext().getResources().getDimensionPixelSize(R.dimen.card_width); | ||
| 62 | if (columns == 0) { | ||
| 63 | columns = 1; | ||
| 64 | } | ||
| 65 | view.getViewTreeObserver().removeOnGlobalLayoutListener(this); | ||
| 66 | GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), columns); | ||
| 67 | mRecyclerView.setLayoutManager(layoutManager); | ||
| 68 | mRecyclerView.setAdapter(mAdapter); | ||
| 69 | } | ||
| 70 | }); | ||
| 71 | } | ||
| 53 | 72 | ||
| 54 | // Add swipe down to refresh gesture | 73 | // Add swipe down to refresh gesture |
| 55 | final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.refresh_grid_games); | 74 | final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.swipe_refresh); |
| 56 | pullToRefresh.setOnRefreshListener(() -> { | 75 | pullToRefresh.setOnRefreshListener(() -> { |
| 57 | GameDatabase databaseHelper = YuzuApplication.databaseHelper; | ||
| 58 | databaseHelper.scanLibrary(databaseHelper.getWritableDatabase()); | ||
| 59 | refresh(); | 76 | refresh(); |
| 60 | pullToRefresh.setRefreshing(false); | 77 | pullToRefresh.setRefreshing(false); |
| 61 | }); | 78 | }); |
| @@ -63,6 +80,8 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam | |||
| 63 | 80 | ||
| 64 | @Override | 81 | @Override |
| 65 | public void refresh() { | 82 | public void refresh() { |
| 83 | GameDatabase databaseHelper = YuzuApplication.databaseHelper; | ||
| 84 | databaseHelper.scanLibrary(databaseHelper.getWritableDatabase()); | ||
| 66 | mPresenter.refresh(); | 85 | mPresenter.refresh(); |
| 67 | updateTextView(); | 86 | updateTextView(); |
| 68 | } | 87 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java index b75dc9a62..fd43575de 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconRequestHandler.java | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | package org.yuzu.yuzu_emu.utils; | 1 | package org.yuzu.yuzu_emu.utils; |
| 2 | 2 | ||
| 3 | import android.graphics.Bitmap; | 3 | import android.graphics.Bitmap; |
| 4 | import android.graphics.BitmapFactory; | ||
| 4 | 5 | ||
| 5 | import com.squareup.picasso.Picasso; | 6 | import com.squareup.picasso.Picasso; |
| 6 | import com.squareup.picasso.Request; | 7 | import com.squareup.picasso.Request; |
| @@ -13,15 +14,16 @@ import java.nio.IntBuffer; | |||
| 13 | public class GameIconRequestHandler extends RequestHandler { | 14 | public class GameIconRequestHandler extends RequestHandler { |
| 14 | @Override | 15 | @Override |
| 15 | public boolean canHandleRequest(Request data) { | 16 | public boolean canHandleRequest(Request data) { |
| 16 | return "iso".equals(data.uri.getScheme()); | 17 | return "content".equals(data.uri.getScheme()); |
| 17 | } | 18 | } |
| 18 | 19 | ||
| 19 | @Override | 20 | @Override |
| 20 | public Result load(Request request, int networkPolicy) { | 21 | public Result load(Request request, int networkPolicy) { |
| 21 | String url = request.uri.getHost() + request.uri.getPath(); | 22 | String gamePath = request.uri.toString(); |
| 22 | int[] vector = NativeLibrary.GetIcon(url); | 23 | byte[] data = NativeLibrary.GetIcon(gamePath); |
| 23 | Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565); | 24 | BitmapFactory.Options options = new BitmapFactory.Options(); |
| 24 | bitmap.copyPixelsFromBuffer(IntBuffer.wrap(vector)); | 25 | options.inMutable = true; |
| 26 | Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); | ||
| 25 | return new Result(bitmap, Picasso.LoadedFrom.DISK); | 27 | return new Result(bitmap, Picasso.LoadedFrom.DISK); |
| 26 | } | 28 | } |
| 27 | } | 29 | } |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java index 5033691b3..504dc5b6d 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/PicassoUtils.java | |||
| @@ -31,7 +31,7 @@ public class PicassoUtils { | |||
| 31 | public static void loadGameIcon(ImageView imageView, String gamePath) { | 31 | public static void loadGameIcon(ImageView imageView, String gamePath) { |
| 32 | Picasso | 32 | Picasso |
| 33 | .get() | 33 | .get() |
| 34 | .load(Uri.parse("iso:/" + gamePath)) | 34 | .load(Uri.parse(gamePath)) |
| 35 | .fit() | 35 | .fit() |
| 36 | .centerInside() | 36 | .centerInside() |
| 37 | .config(Bitmap.Config.RGB_565) | 37 | .config(Bitmap.Config.RGB_565) |
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java index 2dc0f34f3..41b8c6a27 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/viewholders/GameViewHolder.java | |||
| @@ -16,8 +16,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder { | |||
| 16 | private View itemView; | 16 | private View itemView; |
| 17 | public ImageView imageIcon; | 17 | public ImageView imageIcon; |
| 18 | public TextView textGameTitle; | 18 | public TextView textGameTitle; |
| 19 | public TextView textCompany; | 19 | public TextView textGameCaption; |
| 20 | public TextView textFileName; | ||
| 21 | 20 | ||
| 22 | public String gameId; | 21 | public String gameId; |
| 23 | 22 | ||
| @@ -36,8 +35,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder { | |||
| 36 | 35 | ||
| 37 | imageIcon = itemView.findViewById(R.id.image_game_screen); | 36 | imageIcon = itemView.findViewById(R.id.image_game_screen); |
| 38 | textGameTitle = itemView.findViewById(R.id.text_game_title); | 37 | textGameTitle = itemView.findViewById(R.id.text_game_title); |
| 39 | textCompany = itemView.findViewById(R.id.text_company); | 38 | textGameCaption = itemView.findViewById(R.id.text_game_caption); |
| 40 | textFileName = itemView.findViewById(R.id.text_filename); | ||
| 41 | } | 39 | } |
| 42 | 40 | ||
| 43 | public View getItemView() { | 41 | public View getItemView() { |
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 6d1e75c40..2bd908308 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #include "core/file_sys/vfs_real.h" | 24 | #include "core/file_sys/vfs_real.h" |
| 25 | #include "core/hid/hid_core.h" | 25 | #include "core/hid/hid_core.h" |
| 26 | #include "core/hle/service/filesystem/filesystem.h" | 26 | #include "core/hle/service/filesystem/filesystem.h" |
| 27 | #include "core/loader/loader.h" | ||
| 27 | #include "core/perf_stats.h" | 28 | #include "core/perf_stats.h" |
| 28 | #include "jni/config.h" | 29 | #include "jni/config.h" |
| 29 | #include "jni/emu_window/emu_window.h" | 30 | #include "jni/emu_window/emu_window.h" |
| @@ -34,7 +35,11 @@ namespace { | |||
| 34 | 35 | ||
| 35 | class EmulationSession final { | 36 | class EmulationSession final { |
| 36 | public: | 37 | public: |
| 37 | EmulationSession() = default; | 38 | EmulationSession() { |
| 39 | m_system.Initialize(); | ||
| 40 | m_vfs = std::make_shared<FileSys::RealVfsFilesystem>(); | ||
| 41 | } | ||
| 42 | |||
| 38 | ~EmulationSession() = default; | 43 | ~EmulationSession() = default; |
| 39 | 44 | ||
| 40 | static EmulationSession& GetInstance() { | 45 | static EmulationSession& GetInstance() { |
| @@ -42,151 +47,205 @@ public: | |||
| 42 | } | 47 | } |
| 43 | 48 | ||
| 44 | const Core::System& System() const { | 49 | const Core::System& System() const { |
| 45 | return system; | 50 | return m_system; |
| 46 | } | 51 | } |
| 47 | 52 | ||
| 48 | Core::System& System() { | 53 | Core::System& System() { |
| 49 | return system; | 54 | return m_system; |
| 50 | } | 55 | } |
| 51 | 56 | ||
| 52 | const EmuWindow_Android& Window() const { | 57 | const EmuWindow_Android& Window() const { |
| 53 | return *window; | 58 | return *m_window; |
| 54 | } | 59 | } |
| 55 | 60 | ||
| 56 | EmuWindow_Android& Window() { | 61 | EmuWindow_Android& Window() { |
| 57 | return *window; | 62 | return *m_window; |
| 58 | } | 63 | } |
| 59 | 64 | ||
| 60 | ANativeWindow* NativeWindow() const { | 65 | ANativeWindow* NativeWindow() const { |
| 61 | return native_window; | 66 | return m_native_window; |
| 62 | } | 67 | } |
| 63 | 68 | ||
| 64 | void SetNativeWindow(ANativeWindow* native_window_) { | 69 | void SetNativeWindow(ANativeWindow* m_native_window_) { |
| 65 | native_window = native_window_; | 70 | m_native_window = m_native_window_; |
| 66 | } | 71 | } |
| 67 | 72 | ||
| 68 | bool IsRunning() const { | 73 | bool IsRunning() const { |
| 69 | std::scoped_lock lock(mutex); | 74 | std::scoped_lock lock(m_mutex); |
| 70 | return is_running; | 75 | return m_is_running; |
| 71 | } | 76 | } |
| 72 | 77 | ||
| 73 | const Core::PerfStatsResults& PerfStats() const { | 78 | const Core::PerfStatsResults& PerfStats() const { |
| 74 | std::scoped_lock perf_stats_lock(perf_stats_mutex); | 79 | std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
| 75 | return perf_stats; | 80 | return m_perf_stats; |
| 76 | } | 81 | } |
| 77 | 82 | ||
| 78 | void SurfaceChanged() { | 83 | void SurfaceChanged() { |
| 79 | if (!IsRunning()) { | 84 | if (!IsRunning()) { |
| 80 | return; | 85 | return; |
| 81 | } | 86 | } |
| 82 | window->OnSurfaceChanged(native_window); | 87 | m_window->OnSurfaceChanged(m_native_window); |
| 83 | } | 88 | } |
| 84 | 89 | ||
| 85 | Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { | 90 | Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { |
| 86 | std::scoped_lock lock(mutex); | 91 | std::scoped_lock lock(m_mutex); |
| 87 | 92 | ||
| 88 | // Loads the configuration. | 93 | // Loads the configuration. |
| 89 | Config{}; | 94 | Config{}; |
| 90 | 95 | ||
| 91 | // Create the render window. | 96 | // Create the render window. |
| 92 | window = std::make_unique<EmuWindow_Android>(&input_subsystem, native_window); | 97 | m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window); |
| 93 | 98 | ||
| 94 | // Initialize system. | 99 | // Initialize system. |
| 95 | system.SetShuttingDown(false); | 100 | m_system.SetShuttingDown(false); |
| 96 | system.Initialize(); | 101 | m_system.Initialize(); |
| 97 | system.ApplySettings(); | 102 | m_system.ApplySettings(); |
| 98 | system.HIDCore().ReloadInputDevices(); | 103 | m_system.HIDCore().ReloadInputDevices(); |
| 99 | system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); | 104 | m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); |
| 100 | system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); | 105 | m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); |
| 101 | system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); | 106 | m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem()); |
| 102 | 107 | ||
| 103 | // Load the ROM. | 108 | // Load the ROM. |
| 104 | load_result = system.Load(EmulationSession::GetInstance().Window(), filepath); | 109 | m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath); |
| 105 | if (load_result != Core::SystemResultStatus::Success) { | 110 | if (m_load_result != Core::SystemResultStatus::Success) { |
| 106 | return load_result; | 111 | return m_load_result; |
| 107 | } | 112 | } |
| 108 | 113 | ||
| 109 | // Complete initialization. | 114 | // Complete initialization. |
| 110 | system.GPU().Start(); | 115 | m_system.GPU().Start(); |
| 111 | system.GetCpuManager().OnGpuReady(); | 116 | m_system.GetCpuManager().OnGpuReady(); |
| 112 | system.RegisterExitCallback([&] { HaltEmulation(); }); | 117 | m_system.RegisterExitCallback([&] { HaltEmulation(); }); |
| 113 | 118 | ||
| 114 | return Core::SystemResultStatus::Success; | 119 | return Core::SystemResultStatus::Success; |
| 115 | } | 120 | } |
| 116 | 121 | ||
| 117 | void ShutdownEmulation() { | 122 | void ShutdownEmulation() { |
| 118 | std::scoped_lock lock(mutex); | 123 | std::scoped_lock lock(m_mutex); |
| 119 | 124 | ||
| 120 | is_running = false; | 125 | m_is_running = false; |
| 121 | 126 | ||
| 122 | // Unload user input. | 127 | // Unload user input. |
| 123 | system.HIDCore().UnloadInputDevices(); | 128 | m_system.HIDCore().UnloadInputDevices(); |
| 124 | 129 | ||
| 125 | // Shutdown the main emulated process | 130 | // Shutdown the main emulated process |
| 126 | if (load_result == Core::SystemResultStatus::Success) { | 131 | if (m_load_result == Core::SystemResultStatus::Success) { |
| 127 | system.DetachDebugger(); | 132 | m_system.DetachDebugger(); |
| 128 | system.ShutdownMainProcess(); | 133 | m_system.ShutdownMainProcess(); |
| 129 | detached_tasks.WaitForAllTasks(); | 134 | m_detached_tasks.WaitForAllTasks(); |
| 130 | load_result = Core::SystemResultStatus::ErrorNotInitialized; | 135 | m_load_result = Core::SystemResultStatus::ErrorNotInitialized; |
| 131 | } | 136 | } |
| 132 | 137 | ||
| 133 | // Tear down the render window. | 138 | // Tear down the render window. |
| 134 | window.reset(); | 139 | m_window.reset(); |
| 140 | } | ||
| 141 | |||
| 142 | void PauseEmulation() { | ||
| 143 | std::scoped_lock lock(m_mutex); | ||
| 144 | m_system.Pause(); | ||
| 145 | } | ||
| 146 | |||
| 147 | void UnPauseEmulation() { | ||
| 148 | std::scoped_lock lock(m_mutex); | ||
| 149 | m_system.Run(); | ||
| 135 | } | 150 | } |
| 136 | 151 | ||
| 137 | void HaltEmulation() { | 152 | void HaltEmulation() { |
| 138 | std::scoped_lock lock(mutex); | 153 | std::scoped_lock lock(m_mutex); |
| 139 | is_running = false; | 154 | m_is_running = false; |
| 140 | cv.notify_one(); | 155 | m_cv.notify_one(); |
| 141 | } | 156 | } |
| 142 | 157 | ||
| 143 | void RunEmulation() { | 158 | void RunEmulation() { |
| 144 | { | 159 | { |
| 145 | std::scoped_lock lock(mutex); | 160 | std::scoped_lock lock(m_mutex); |
| 146 | is_running = true; | 161 | m_is_running = true; |
| 147 | } | 162 | } |
| 148 | 163 | ||
| 149 | void(system.Run()); | 164 | void(m_system.Run()); |
| 150 | 165 | ||
| 151 | if (system.DebuggerEnabled()) { | 166 | if (m_system.DebuggerEnabled()) { |
| 152 | system.InitializeDebugger(); | 167 | m_system.InitializeDebugger(); |
| 153 | } | 168 | } |
| 154 | 169 | ||
| 155 | while (true) { | 170 | while (true) { |
| 156 | { | 171 | { |
| 157 | std::unique_lock lock(mutex); | 172 | std::unique_lock lock(m_mutex); |
| 158 | if (cv.wait_for(lock, std::chrono::milliseconds(100), | 173 | if (m_cv.wait_for(lock, std::chrono::milliseconds(100), |
| 159 | [&]() { return !is_running; })) { | 174 | [&]() { return !m_is_running; })) { |
| 160 | // Emulation halted. | 175 | // Emulation halted. |
| 161 | break; | 176 | break; |
| 162 | } | 177 | } |
| 163 | } | 178 | } |
| 164 | { | 179 | { |
| 165 | // Refresh performance stats. | 180 | // Refresh performance stats. |
| 166 | std::scoped_lock perf_stats_lock(perf_stats_mutex); | 181 | std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); |
| 167 | perf_stats = system.GetAndResetPerfStats(); | 182 | m_perf_stats = m_system.GetAndResetPerfStats(); |
| 168 | } | 183 | } |
| 169 | } | 184 | } |
| 170 | } | 185 | } |
| 171 | 186 | ||
| 187 | std::string GetRomTitle(const std::string& path) { | ||
| 188 | return GetRomMetadata(path).title; | ||
| 189 | } | ||
| 190 | |||
| 191 | std::vector<u8> GetRomIcon(const std::string& path) { | ||
| 192 | return GetRomMetadata(path).icon; | ||
| 193 | } | ||
| 194 | |||
| 195 | void ResetRomMetadata() { | ||
| 196 | m_rom_metadata_cache.clear(); | ||
| 197 | } | ||
| 198 | |||
| 172 | private: | 199 | private: |
| 173 | static EmulationSession s_instance; | 200 | struct RomMetadata { |
| 201 | std::string title; | ||
| 202 | std::vector<u8> icon; | ||
| 203 | }; | ||
| 204 | |||
| 205 | RomMetadata GetRomMetadata(const std::string& path) { | ||
| 206 | if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { | ||
| 207 | return search->second; | ||
| 208 | } | ||
| 209 | |||
| 210 | return CacheRomMetadata(path); | ||
| 211 | } | ||
| 174 | 212 | ||
| 175 | ANativeWindow* native_window{}; | 213 | RomMetadata CacheRomMetadata(const std::string& path) { |
| 214 | const auto file = Core::GetGameFileFromPath(m_vfs, path); | ||
| 215 | const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); | ||
| 176 | 216 | ||
| 177 | InputCommon::InputSubsystem input_subsystem; | 217 | RomMetadata entry; |
| 178 | Common::DetachedTasks detached_tasks; | 218 | loader->ReadTitle(entry.title); |
| 179 | Core::System system; | 219 | loader->ReadIcon(entry.icon); |
| 180 | 220 | ||
| 181 | Core::PerfStatsResults perf_stats{}; | 221 | m_rom_metadata_cache[path] = entry; |
| 182 | 222 | ||
| 183 | std::unique_ptr<EmuWindow_Android> window; | 223 | return entry; |
| 184 | std::condition_variable_any cv; | 224 | } |
| 185 | bool is_running{}; | 225 | |
| 186 | Core::SystemResultStatus load_result{Core::SystemResultStatus::ErrorNotInitialized}; | 226 | private: |
| 227 | static EmulationSession s_instance; | ||
| 187 | 228 | ||
| 188 | mutable std::mutex perf_stats_mutex; | 229 | // Frontend management |
| 189 | mutable std::mutex mutex; | 230 | std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache; |
| 231 | |||
| 232 | // Window management | ||
| 233 | std::unique_ptr<EmuWindow_Android> m_window; | ||
| 234 | ANativeWindow* m_native_window{}; | ||
| 235 | |||
| 236 | // Core emulation | ||
| 237 | Core::System m_system; | ||
| 238 | InputCommon::InputSubsystem m_input_subsystem; | ||
| 239 | Common::DetachedTasks m_detached_tasks; | ||
| 240 | Core::PerfStatsResults m_perf_stats{}; | ||
| 241 | std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs; | ||
| 242 | Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized}; | ||
| 243 | bool m_is_running{}; | ||
| 244 | |||
| 245 | // Synchronization | ||
| 246 | std::condition_variable_any m_cv; | ||
| 247 | mutable std::mutex m_perf_stats_mutex; | ||
| 248 | mutable std::mutex m_mutex; | ||
| 190 | }; | 249 | }; |
| 191 | 250 | ||
| 192 | /*static*/ EmulationSession EmulationSession::s_instance; | 251 | /*static*/ EmulationSession EmulationSession::s_instance; |
| @@ -275,16 +334,25 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env, | |||
| 275 | } | 334 | } |
| 276 | 335 | ||
| 277 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation([[maybe_unused]] JNIEnv* env, | 336 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation([[maybe_unused]] JNIEnv* env, |
| 278 | [[maybe_unused]] jclass clazz) {} | 337 | [[maybe_unused]] jclass clazz) { |
| 338 | EmulationSession::GetInstance().UnPauseEmulation(); | ||
| 339 | } | ||
| 279 | 340 | ||
| 280 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation([[maybe_unused]] JNIEnv* env, | 341 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation([[maybe_unused]] JNIEnv* env, |
| 281 | [[maybe_unused]] jclass clazz) {} | 342 | [[maybe_unused]] jclass clazz) { |
| 343 | EmulationSession::GetInstance().PauseEmulation(); | ||
| 344 | } | ||
| 282 | 345 | ||
| 283 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation([[maybe_unused]] JNIEnv* env, | 346 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation([[maybe_unused]] JNIEnv* env, |
| 284 | [[maybe_unused]] jclass clazz) { | 347 | [[maybe_unused]] jclass clazz) { |
| 285 | EmulationSession::GetInstance().HaltEmulation(); | 348 | EmulationSession::GetInstance().HaltEmulation(); |
| 286 | } | 349 | } |
| 287 | 350 | ||
| 351 | void Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata([[maybe_unused]] JNIEnv* env, | ||
| 352 | [[maybe_unused]] jclass clazz) { | ||
| 353 | EmulationSession::GetInstance().ResetRomMetadata(); | ||
| 354 | } | ||
| 355 | |||
| 288 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning([[maybe_unused]] JNIEnv* env, | 356 | jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning([[maybe_unused]] JNIEnv* env, |
| 289 | [[maybe_unused]] jclass clazz) { | 357 | [[maybe_unused]] jclass clazz) { |
| 290 | return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); | 358 | return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); |
| @@ -347,16 +415,21 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv* | |||
| 347 | } | 415 | } |
| 348 | } | 416 | } |
| 349 | 417 | ||
| 350 | jintArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env, | 418 | jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env, |
| 351 | [[maybe_unused]] jclass clazz, | 419 | [[maybe_unused]] jclass clazz, |
| 352 | [[maybe_unused]] jstring j_file) { | 420 | [[maybe_unused]] jstring j_filename) { |
| 353 | return {}; | 421 | auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename)); |
| 422 | jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size())); | ||
| 423 | env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), | ||
| 424 | reinterpret_cast<jbyte*>(icon_data.data())); | ||
| 425 | return icon; | ||
| 354 | } | 426 | } |
| 355 | 427 | ||
| 356 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle([[maybe_unused]] JNIEnv* env, | 428 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle([[maybe_unused]] JNIEnv* env, |
| 357 | [[maybe_unused]] jclass clazz, | 429 | [[maybe_unused]] jclass clazz, |
| 358 | [[maybe_unused]] jstring j_filename) { | 430 | [[maybe_unused]] jstring j_filename) { |
| 359 | return env->NewStringUTF(""); | 431 | auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename)); |
| 432 | return env->NewStringUTF(title.c_str()); | ||
| 360 | } | 433 | } |
| 361 | 434 | ||
| 362 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription([[maybe_unused]] JNIEnv* env, | 435 | jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription([[maybe_unused]] JNIEnv* env, |
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 210976201..bbc783aa8 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h | |||
| @@ -19,6 +19,9 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIE | |||
| 19 | JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env, | 19 | JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env, |
| 20 | jclass clazz); | 20 | jclass clazz); |
| 21 | 21 | ||
| 22 | JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env, | ||
| 23 | jclass clazz); | ||
| 24 | |||
| 22 | JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env, | 25 | JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env, |
| 23 | jclass clazz); | 26 | jclass clazz); |
| 24 | 27 | ||
| @@ -39,8 +42,9 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JN | |||
| 39 | JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, | 42 | JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz, |
| 40 | jfloat x, jfloat y); | 43 | jfloat x, jfloat y); |
| 41 | 44 | ||
| 42 | JNIEXPORT jintArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env, jclass clazz, | 45 | JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env, |
| 43 | jstring j_file); | 46 | jclass clazz, |
| 47 | jstring j_file); | ||
| 44 | 48 | ||
| 45 | JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz, | 49 | JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz, |
| 46 | jstring j_filename); | 50 | jstring j_filename); |
diff --git a/src/android/app/src/main/res/layout/card_game.xml b/src/android/app/src/main/res/layout/card_game.xml index 217f02d34..a0d453719 100644 --- a/src/android/app/src/main/res/layout/card_game.xml +++ b/src/android/app/src/main/res/layout/card_game.xml | |||
| @@ -1,81 +1,76 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <androidx.constraintlayout.widget.ConstraintLayout |
| 3 | xmlns:android="http://schemas.android.com/apk/res/android" | ||
| 3 | xmlns:app="http://schemas.android.com/apk/res-auto" | 4 | xmlns:app="http://schemas.android.com/apk/res-auto" |
| 4 | xmlns:tools="http://schemas.android.com/tools" | 5 | xmlns:tools="http://schemas.android.com/tools" |
| 5 | android:layout_width="match_parent" | 6 | android:layout_width="match_parent" |
| 6 | android:layout_height="wrap_content" | 7 | android:layout_height="wrap_content" |
| 8 | android:background="?attr/selectableItemBackground" | ||
| 7 | android:clickable="true" | 9 | android:clickable="true" |
| 10 | android:clipToPadding="false" | ||
| 8 | android:focusable="true" | 11 | android:focusable="true" |
| 9 | android:foreground="?android:attr/selectableItemBackground" | 12 | android:paddingStart="4dp" |
| 10 | android:transitionName="card_game" | 13 | android:paddingTop="8dp" |
| 11 | tools:layout_width="match_parent"> | 14 | android:paddingEnd="4dp" |
| 15 | android:paddingBottom="8dp" | ||
| 16 | android:transitionName="card_game"> | ||
| 12 | 17 | ||
| 13 | <androidx.constraintlayout.widget.ConstraintLayout | 18 | <androidx.cardview.widget.CardView |
| 14 | android:id="@+id/linearLayout" | 19 | android:id="@+id/card_game_art" |
| 15 | android:layout_width="match_parent" | 20 | android:layout_width="150dp" |
| 16 | android:layout_height="wrap_content" | 21 | android:layout_height="150dp" |
| 17 | android:padding="8dp"> | 22 | app:cardCornerRadius="4dp" |
| 23 | app:layout_constraintEnd_toEndOf="parent" | ||
| 24 | app:layout_constraintStart_toStartOf="parent" | ||
| 25 | app:layout_constraintTop_toTopOf="parent"> | ||
| 18 | 26 | ||
| 19 | <ImageView | 27 | <ImageView |
| 20 | android:id="@+id/image_game_screen" | 28 | android:id="@+id/image_game_screen" |
| 21 | android:layout_width="56dp" | 29 | android:layout_width="match_parent" |
| 22 | android:layout_height="56dp" | 30 | android:layout_height="match_parent" |
| 23 | android:adjustViewBounds="false" | 31 | android:layout_weight="1" /> |
| 24 | android:cropToPadding="false" | ||
| 25 | android:scaleType="fitCenter" | ||
| 26 | app:layout_constraintBottom_toBottomOf="parent" | ||
| 27 | app:layout_constraintStart_toStartOf="parent" | ||
| 28 | app:layout_constraintTop_toTopOf="parent" | ||
| 29 | tools:scaleType="fitCenter" /> | ||
| 30 | 32 | ||
| 31 | <TextView | 33 | <TextView |
| 32 | android:id="@+id/text_game_title" | 34 | android:id="@+id/text_game_title_inner" |
| 33 | style="@android:style/TextAppearance.Material.Subhead" | 35 | style="@android:style/TextAppearance.Material.Subhead" |
| 34 | android:layout_width="0dp" | 36 | android:layout_width="match_parent" |
| 35 | android:layout_height="wrap_content" | 37 | android:layout_height="match_parent" |
| 36 | android:layout_marginStart="8dp" | ||
| 37 | android:baselineAligned="false" | ||
| 38 | android:ellipsize="end" | 38 | android:ellipsize="end" |
| 39 | android:gravity="center_vertical" | 39 | android:gravity="center|top" |
| 40 | android:lines="1" | 40 | android:maxLines="2" |
| 41 | android:maxLines="1" | 41 | android:paddingLeft="2dp" |
| 42 | android:textAlignment="viewStart" | 42 | android:paddingRight="2dp" |
| 43 | app:layout_constraintEnd_toEndOf="parent" | 43 | android:paddingTop="8dp" |
| 44 | app:layout_constraintStart_toEndOf="@+id/image_game_screen" | 44 | android:visibility="visible" |
| 45 | app:layout_constraintTop_toTopOf="parent" | 45 | tools:text="The Legend of Zelda: The Wind Waker" /> |
| 46 | tools:text="The Legend of Zelda\nOcarina of Time 3D" | ||
| 47 | android:textColor="@color/header_text" /> | ||
| 48 | 46 | ||
| 49 | <TextView | 47 | </androidx.cardview.widget.CardView> |
| 50 | android:id="@+id/text_company" | ||
| 51 | style="@android:style/TextAppearance.Material.Caption" | ||
| 52 | android:layout_width="wrap_content" | ||
| 53 | android:layout_height="wrap_content" | ||
| 54 | android:ellipsize="end" | ||
| 55 | android:lines="1" | ||
| 56 | android:maxLines="1" | ||
| 57 | app:layout_constraintBottom_toBottomOf="@+id/image_game_screen" | ||
| 58 | app:layout_constraintStart_toStartOf="@+id/text_game_title" | ||
| 59 | app:layout_constraintTop_toBottomOf="@+id/text_game_title" | ||
| 60 | app:layout_constraintVertical_bias="0.842" | ||
| 61 | tools:text="Nintendo" | ||
| 62 | android:textColor="@color/header_subtext" /> | ||
| 63 | 48 | ||
| 64 | <TextView | 49 | <TextView |
| 65 | android:id="@+id/text_filename" | 50 | android:id="@+id/text_game_title" |
| 66 | style="@android:style/TextAppearance.Material.Caption" | 51 | style="@android:style/TextAppearance.Material.Subhead" |
| 67 | android:layout_width="wrap_content" | 52 | android:layout_width="150dp" |
| 68 | android:layout_height="wrap_content" | 53 | android:layout_height="wrap_content" |
| 69 | android:ellipsize="end" | 54 | android:ellipsize="end" |
| 70 | android:lines="1" | 55 | android:maxLines="2" |
| 71 | android:maxLines="1" | 56 | android:paddingTop="8dp" |
| 72 | app:layout_constraintBottom_toBottomOf="@+id/image_game_screen" | 57 | app:layout_constraintEnd_toEndOf="@+id/card_game_art" |
| 73 | app:layout_constraintStart_toStartOf="@+id/text_game_title" | 58 | app:layout_constraintStart_toStartOf="@+id/card_game_art" |
| 74 | app:layout_constraintTop_toBottomOf="@+id/text_game_title" | 59 | app:layout_constraintTop_toBottomOf="@+id/card_game_art" |
| 75 | app:layout_constraintVertical_bias="0.0" | 60 | tools:text="The Legend of Zelda: The Wind Waker" /> |
| 76 | tools:text="Pilotwings_Resort.cxi" | ||
| 77 | android:textColor="@color/header_subtext" /> | ||
| 78 | 61 | ||
| 79 | </androidx.constraintlayout.widget.ConstraintLayout> | 62 | <TextView |
| 63 | android:id="@+id/text_game_caption" | ||
| 64 | style="@android:style/TextAppearance.Material.Caption" | ||
| 65 | android:layout_width="150dp" | ||
| 66 | android:layout_height="wrap_content" | ||
| 67 | android:ellipsize="end" | ||
| 68 | android:lines="1" | ||
| 69 | android:maxLines="1" | ||
| 70 | android:paddingTop="8dp" | ||
| 71 | app:layout_constraintEnd_toEndOf="@+id/card_game_art" | ||
| 72 | app:layout_constraintStart_toStartOf="@+id/card_game_art" | ||
| 73 | app:layout_constraintTop_toBottomOf="@+id/text_game_title" | ||
| 74 | tools:text="Nintendo" /> | ||
| 80 | 75 | ||
| 81 | </androidx.cardview.widget.CardView> | 76 | </androidx.constraintlayout.widget.ConstraintLayout> |
diff --git a/src/android/app/src/main/res/layout/fragment_grid.xml b/src/android/app/src/main/res/layout/fragment_grid.xml index f5b6c2e19..01399e18d 100644 --- a/src/android/app/src/main/res/layout/fragment_grid.xml +++ b/src/android/app/src/main/res/layout/fragment_grid.xml | |||
| @@ -1,12 +1,12 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | 2 | <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
| 3 | xmlns:tools="http://schemas.android.com/tools" | 3 | xmlns:tools="http://schemas.android.com/tools" |
| 4 | android:layout_width="match_parent" | 4 | android:layout_width="match_parent" |
| 5 | android:layout_height="match_parent"> | 5 | android:layout_height="match_parent"> |
| 6 | 6 | ||
| 7 | <androidx.swiperefreshlayout.widget.SwipeRefreshLayout | 7 | <androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
| 8 | android:id="@+id/refresh_grid_games" | 8 | android:id="@+id/swipe_refresh" |
| 9 | android:layout_width="match_parent" | 9 | android:layout_width="wrap_content" |
| 10 | android:layout_height="wrap_content"> | 10 | android:layout_height="wrap_content"> |
| 11 | 11 | ||
| 12 | <RelativeLayout | 12 | <RelativeLayout |
| @@ -22,12 +22,13 @@ | |||
| 22 | android:textSize="18sp" | 22 | android:textSize="18sp" |
| 23 | android:gravity="center" /> | 23 | android:gravity="center" /> |
| 24 | 24 | ||
| 25 | <androidx.recyclerview.widget.RecyclerView | 25 | <androidx.recyclerview.widget.RecyclerView |
| 26 | android:id="@+id/grid_games" | 26 | android:id="@+id/grid_games" |
| 27 | android:layout_width="match_parent" | 27 | android:layout_width="match_parent" |
| 28 | android:layout_height="match_parent" | 28 | android:layout_height="match_parent" |
| 29 | tools:listitem="@layout/card_game" /> | 29 | android:clipToPadding="false" |
| 30 | tools:listitem="@layout/card_game" /> | ||
| 30 | </RelativeLayout> | 31 | </RelativeLayout> |
| 31 | 32 | ||
| 32 | </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> | 33 | </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
| 33 | </FrameLayout> \ No newline at end of file | 34 | </FrameLayout> |
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml index a3bb0c2c5..0b028a167 100644 --- a/src/android/app/src/main/res/values/dimens.xml +++ b/src/android/app/src/main/res/values/dimens.xml | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | <dimen name="spacing_list">64dp</dimen> | 6 | <dimen name="spacing_list">64dp</dimen> |
| 7 | <dimen name="spacing_fab">72dp</dimen> | 7 | <dimen name="spacing_fab">72dp</dimen> |
| 8 | <dimen name="menu_width">256dp</dimen> | 8 | <dimen name="menu_width">256dp</dimen> |
| 9 | <dimen name="card_width">135dp</dimen> | 9 | <dimen name="card_width">150dp</dimen> |
| 10 | 10 | ||
| 11 | <dimen name="dialog_margin">20dp</dimen> | 11 | <dimen name="dialog_margin">20dp</dimen> |
| 12 | <dimen name="elevated_app_bar">3dp</dimen> | 12 | <dimen name="elevated_app_bar">3dp</dimen> |