mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-11-04 00:49:02 -06:00 
			
		
		
		
	android: frontend: Implement game grid view. (#9)
This commit is contained in:
		@@ -11,7 +11,7 @@ def abiFilter = "arm64-v8a" //, "x86"
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileSdkVersion 32
 | 
			
		||||
    ndkVersion "25.1.8937393"
 | 
			
		||||
    ndkVersion "25.2.9519653"
 | 
			
		||||
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
 
 | 
			
		||||
@@ -141,9 +141,9 @@ public final class NativeLibrary {
 | 
			
		||||
     * Gets the embedded icon within the given ROM.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filename the file path to the ROM.
 | 
			
		||||
     * @return an integer array containing the color data for the icon.
 | 
			
		||||
     * @return a byte array containing the JPEG data for the icon.
 | 
			
		||||
     */
 | 
			
		||||
    public static native int[] GetIcon(String filename);
 | 
			
		||||
    public static native byte[] GetIcon(String filename);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the embedded title of the given ISO/ROM.
 | 
			
		||||
@@ -204,6 +204,11 @@ public final class NativeLibrary {
 | 
			
		||||
     */
 | 
			
		||||
    public static native void StopEmulation();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Resets the in-memory ROM metadata cache.
 | 
			
		||||
     */
 | 
			
		||||
    public static native void ResetRomMetadata();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true if emulation is running (or is paused).
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -86,11 +86,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
 | 
			
		||||
                        mCursor.getString(GameDatabase.GAME_COLUMN_PATH));
 | 
			
		||||
 | 
			
		||||
                holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE).replaceAll("[\\t\\n\\r]+", " "));
 | 
			
		||||
                holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
 | 
			
		||||
 | 
			
		||||
                String filepath = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
 | 
			
		||||
                String filename = FileUtil.getFilename(YuzuApplication.getAppContext(), filepath);
 | 
			
		||||
                holder.textFileName.setText(filename);
 | 
			
		||||
                holder.textGameCaption.setText(mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION));
 | 
			
		||||
 | 
			
		||||
                // TODO These shouldn't be necessary once the move to a DB-based model is complete.
 | 
			
		||||
                holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID);
 | 
			
		||||
@@ -98,7 +94,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> impl
 | 
			
		||||
                holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE);
 | 
			
		||||
                holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION);
 | 
			
		||||
                holder.regions = mCursor.getString(GameDatabase.GAME_COLUMN_REGIONS);
 | 
			
		||||
                holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY);
 | 
			
		||||
                holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_CAPTION);
 | 
			
		||||
 | 
			
		||||
                final int backgroundColorId = isValidGame(holder.path) ? R.color.view_background : R.color.view_disabled;
 | 
			
		||||
                View itemView = holder.getItemView();
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ public final class Game {
 | 
			
		||||
                cursor.getString(GameDatabase.GAME_COLUMN_REGIONS),
 | 
			
		||||
                cursor.getString(GameDatabase.GAME_COLUMN_PATH),
 | 
			
		||||
                cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID),
 | 
			
		||||
                cursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
 | 
			
		||||
                cursor.getString(GameDatabase.GAME_COLUMN_CAPTION));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getTitle() {
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ public final class GameDatabase extends SQLiteOpenHelper {
 | 
			
		||||
    public static final int GAME_COLUMN_DESCRIPTION = 3;
 | 
			
		||||
    public static final int GAME_COLUMN_REGIONS = 4;
 | 
			
		||||
    public static final int GAME_COLUMN_GAME_ID = 5;
 | 
			
		||||
    public static final int GAME_COLUMN_COMPANY = 6;
 | 
			
		||||
    public static final int GAME_COLUMN_CAPTION = 6;
 | 
			
		||||
    public static final int FOLDER_COLUMN_PATH = 1;
 | 
			
		||||
    public static final String KEY_DB_ID = "_id";
 | 
			
		||||
    public static final String KEY_GAME_PATH = "path";
 | 
			
		||||
@@ -176,6 +176,9 @@ public final class GameDatabase extends SQLiteOpenHelper {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Ensure keys are loaded so that ROM metadata can be decrypted.
 | 
			
		||||
        NativeLibrary.ReloadKeys();
 | 
			
		||||
 | 
			
		||||
        MinimalDocumentFile[] children = FileUtil.listFiles(context, parent);
 | 
			
		||||
        for (MinimalDocumentFile file : children) {
 | 
			
		||||
            if (file.isDirectory()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -161,6 +161,7 @@ public final class MainActivity extends AppCompatActivity implements MainView {
 | 
			
		||||
                    if (FileUtil.copyUriToInternalStorage(this, result.getData(), dstPath, "prod.keys")) {
 | 
			
		||||
                        if (NativeLibrary.ReloadKeys()) {
 | 
			
		||||
                            Toast.makeText(this, R.string.install_keys_success, Toast.LENGTH_SHORT).show();
 | 
			
		||||
                            refreshFragment();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Toast.makeText(this, R.string.install_keys_failure, Toast.LENGTH_SHORT).show();
 | 
			
		||||
                            launchFileListActivity(MainPresenter.REQUEST_INSTALL_KEYS);
 | 
			
		||||
@@ -184,6 +185,7 @@ public final class MainActivity extends AppCompatActivity implements MainView {
 | 
			
		||||
 | 
			
		||||
    private void refreshFragment() {
 | 
			
		||||
        if (mPlatformGamesFragment != null) {
 | 
			
		||||
            NativeLibrary.ResetRomMetadata();
 | 
			
		||||
            mPlatformGamesFragment.refresh();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import android.os.Bundle;
 | 
			
		||||
import android.view.LayoutInflater;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.ViewGroup;
 | 
			
		||||
import android.view.ViewTreeObserver;
 | 
			
		||||
import android.widget.TextView;
 | 
			
		||||
 | 
			
		||||
import androidx.core.content.ContextCompat;
 | 
			
		||||
@@ -13,6 +14,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView;
 | 
			
		||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 | 
			
		||||
 | 
			
		||||
import org.yuzu.yuzu_emu.NativeLibrary;
 | 
			
		||||
import org.yuzu.yuzu_emu.YuzuApplication;
 | 
			
		||||
import org.yuzu.yuzu_emu.R;
 | 
			
		||||
import org.yuzu.yuzu_emu.adapters.GameAdapter;
 | 
			
		||||
@@ -43,19 +45,34 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onViewCreated(View view, Bundle savedInstanceState) {
 | 
			
		||||
        int columns = getResources().getInteger(R.integer.game_grid_columns);
 | 
			
		||||
        RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
 | 
			
		||||
        mAdapter = new GameAdapter();
 | 
			
		||||
 | 
			
		||||
        mRecyclerView.setLayoutManager(layoutManager);
 | 
			
		||||
        mRecyclerView.setAdapter(mAdapter);
 | 
			
		||||
        mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(ContextCompat.getDrawable(getActivity(), R.drawable.gamelist_divider), 1));
 | 
			
		||||
        // Organize our grid layout based on the current view.
 | 
			
		||||
        if (isAdded()) {
 | 
			
		||||
            view.getViewTreeObserver()
 | 
			
		||||
                    .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
 | 
			
		||||
                        @Override
 | 
			
		||||
                        public void onGlobalLayout() {
 | 
			
		||||
                            if (view.getMeasuredWidth() == 0) {
 | 
			
		||||
                                return;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            int columns = view.getMeasuredWidth() /
 | 
			
		||||
                                    requireContext().getResources().getDimensionPixelSize(R.dimen.card_width);
 | 
			
		||||
                            if (columns == 0) {
 | 
			
		||||
                                columns = 1;
 | 
			
		||||
                            }
 | 
			
		||||
                            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
 | 
			
		||||
                            GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
 | 
			
		||||
                            mRecyclerView.setLayoutManager(layoutManager);
 | 
			
		||||
                            mRecyclerView.setAdapter(mAdapter);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add swipe down to refresh gesture
 | 
			
		||||
        final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.refresh_grid_games);
 | 
			
		||||
        final SwipeRefreshLayout pullToRefresh = view.findViewById(R.id.swipe_refresh);
 | 
			
		||||
        pullToRefresh.setOnRefreshListener(() -> {
 | 
			
		||||
            GameDatabase databaseHelper = YuzuApplication.databaseHelper;
 | 
			
		||||
            databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
 | 
			
		||||
            refresh();
 | 
			
		||||
            pullToRefresh.setRefreshing(false);
 | 
			
		||||
        });
 | 
			
		||||
@@ -63,6 +80,8 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void refresh() {
 | 
			
		||||
        GameDatabase databaseHelper = YuzuApplication.databaseHelper;
 | 
			
		||||
        databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
 | 
			
		||||
        mPresenter.refresh();
 | 
			
		||||
        updateTextView();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package org.yuzu.yuzu_emu.utils;
 | 
			
		||||
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.BitmapFactory;
 | 
			
		||||
 | 
			
		||||
import com.squareup.picasso.Picasso;
 | 
			
		||||
import com.squareup.picasso.Request;
 | 
			
		||||
@@ -13,15 +14,16 @@ import java.nio.IntBuffer;
 | 
			
		||||
public class GameIconRequestHandler extends RequestHandler {
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean canHandleRequest(Request data) {
 | 
			
		||||
        return "iso".equals(data.uri.getScheme());
 | 
			
		||||
        return "content".equals(data.uri.getScheme());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Result load(Request request, int networkPolicy) {
 | 
			
		||||
        String url = request.uri.getHost() + request.uri.getPath();
 | 
			
		||||
        int[] vector = NativeLibrary.GetIcon(url);
 | 
			
		||||
        Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565);
 | 
			
		||||
        bitmap.copyPixelsFromBuffer(IntBuffer.wrap(vector));
 | 
			
		||||
        String gamePath = request.uri.toString();
 | 
			
		||||
        byte[] data = NativeLibrary.GetIcon(gamePath);
 | 
			
		||||
        BitmapFactory.Options options = new BitmapFactory.Options();
 | 
			
		||||
        options.inMutable = true;
 | 
			
		||||
        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
 | 
			
		||||
        return new Result(bitmap, Picasso.LoadedFrom.DISK);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ public class PicassoUtils {
 | 
			
		||||
    public static void loadGameIcon(ImageView imageView, String gamePath) {
 | 
			
		||||
        Picasso
 | 
			
		||||
                .get()
 | 
			
		||||
                .load(Uri.parse("iso:/" + gamePath))
 | 
			
		||||
                .load(Uri.parse(gamePath))
 | 
			
		||||
                .fit()
 | 
			
		||||
                .centerInside()
 | 
			
		||||
                .config(Bitmap.Config.RGB_565)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder {
 | 
			
		||||
    private View itemView;
 | 
			
		||||
    public ImageView imageIcon;
 | 
			
		||||
    public TextView textGameTitle;
 | 
			
		||||
    public TextView textCompany;
 | 
			
		||||
    public TextView textFileName;
 | 
			
		||||
    public TextView textGameCaption;
 | 
			
		||||
 | 
			
		||||
    public String gameId;
 | 
			
		||||
 | 
			
		||||
@@ -36,8 +35,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder {
 | 
			
		||||
 | 
			
		||||
        imageIcon = itemView.findViewById(R.id.image_game_screen);
 | 
			
		||||
        textGameTitle = itemView.findViewById(R.id.text_game_title);
 | 
			
		||||
        textCompany = itemView.findViewById(R.id.text_company);
 | 
			
		||||
        textFileName = itemView.findViewById(R.id.text_filename);
 | 
			
		||||
        textGameCaption = itemView.findViewById(R.id.text_game_caption);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public View getItemView() {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@
 | 
			
		||||
#include "core/file_sys/vfs_real.h"
 | 
			
		||||
#include "core/hid/hid_core.h"
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/perf_stats.h"
 | 
			
		||||
#include "jni/config.h"
 | 
			
		||||
#include "jni/emu_window/emu_window.h"
 | 
			
		||||
@@ -34,7 +35,11 @@ namespace {
 | 
			
		||||
 | 
			
		||||
class EmulationSession final {
 | 
			
		||||
public:
 | 
			
		||||
    EmulationSession() = default;
 | 
			
		||||
    EmulationSession() {
 | 
			
		||||
        m_system.Initialize();
 | 
			
		||||
        m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~EmulationSession() = default;
 | 
			
		||||
 | 
			
		||||
    static EmulationSession& GetInstance() {
 | 
			
		||||
@@ -42,151 +47,205 @@ public:
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Core::System& System() const {
 | 
			
		||||
        return system;
 | 
			
		||||
        return m_system;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Core::System& System() {
 | 
			
		||||
        return system;
 | 
			
		||||
        return m_system;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const EmuWindow_Android& Window() const {
 | 
			
		||||
        return *window;
 | 
			
		||||
        return *m_window;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    EmuWindow_Android& Window() {
 | 
			
		||||
        return *window;
 | 
			
		||||
        return *m_window;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ANativeWindow* NativeWindow() const {
 | 
			
		||||
        return native_window;
 | 
			
		||||
        return m_native_window;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetNativeWindow(ANativeWindow* native_window_) {
 | 
			
		||||
        native_window = native_window_;
 | 
			
		||||
    void SetNativeWindow(ANativeWindow* m_native_window_) {
 | 
			
		||||
        m_native_window = m_native_window_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool IsRunning() const {
 | 
			
		||||
        std::scoped_lock lock(mutex);
 | 
			
		||||
        return is_running;
 | 
			
		||||
        std::scoped_lock lock(m_mutex);
 | 
			
		||||
        return m_is_running;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const Core::PerfStatsResults& PerfStats() const {
 | 
			
		||||
        std::scoped_lock perf_stats_lock(perf_stats_mutex);
 | 
			
		||||
        return perf_stats;
 | 
			
		||||
        std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
 | 
			
		||||
        return m_perf_stats;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SurfaceChanged() {
 | 
			
		||||
        if (!IsRunning()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        window->OnSurfaceChanged(native_window);
 | 
			
		||||
        m_window->OnSurfaceChanged(m_native_window);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
 | 
			
		||||
        std::scoped_lock lock(mutex);
 | 
			
		||||
        std::scoped_lock lock(m_mutex);
 | 
			
		||||
 | 
			
		||||
        // Loads the configuration.
 | 
			
		||||
        Config{};
 | 
			
		||||
 | 
			
		||||
        // Create the render window.
 | 
			
		||||
        window = std::make_unique<EmuWindow_Android>(&input_subsystem, native_window);
 | 
			
		||||
        m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window);
 | 
			
		||||
 | 
			
		||||
        // Initialize system.
 | 
			
		||||
        system.SetShuttingDown(false);
 | 
			
		||||
        system.Initialize();
 | 
			
		||||
        system.ApplySettings();
 | 
			
		||||
        system.HIDCore().ReloadInputDevices();
 | 
			
		||||
        system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
 | 
			
		||||
        system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
 | 
			
		||||
        system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
 | 
			
		||||
        m_system.SetShuttingDown(false);
 | 
			
		||||
        m_system.Initialize();
 | 
			
		||||
        m_system.ApplySettings();
 | 
			
		||||
        m_system.HIDCore().ReloadInputDevices();
 | 
			
		||||
        m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
 | 
			
		||||
        m_system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
 | 
			
		||||
        m_system.GetFileSystemController().CreateFactories(*m_system.GetFilesystem());
 | 
			
		||||
 | 
			
		||||
        // Load the ROM.
 | 
			
		||||
        load_result = system.Load(EmulationSession::GetInstance().Window(), filepath);
 | 
			
		||||
        if (load_result != Core::SystemResultStatus::Success) {
 | 
			
		||||
            return load_result;
 | 
			
		||||
        m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
 | 
			
		||||
        if (m_load_result != Core::SystemResultStatus::Success) {
 | 
			
		||||
            return m_load_result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Complete initialization.
 | 
			
		||||
        system.GPU().Start();
 | 
			
		||||
        system.GetCpuManager().OnGpuReady();
 | 
			
		||||
        system.RegisterExitCallback([&] { HaltEmulation(); });
 | 
			
		||||
        m_system.GPU().Start();
 | 
			
		||||
        m_system.GetCpuManager().OnGpuReady();
 | 
			
		||||
        m_system.RegisterExitCallback([&] { HaltEmulation(); });
 | 
			
		||||
 | 
			
		||||
        return Core::SystemResultStatus::Success;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShutdownEmulation() {
 | 
			
		||||
        std::scoped_lock lock(mutex);
 | 
			
		||||
        std::scoped_lock lock(m_mutex);
 | 
			
		||||
 | 
			
		||||
        is_running = false;
 | 
			
		||||
        m_is_running = false;
 | 
			
		||||
 | 
			
		||||
        // Unload user input.
 | 
			
		||||
        system.HIDCore().UnloadInputDevices();
 | 
			
		||||
        m_system.HIDCore().UnloadInputDevices();
 | 
			
		||||
 | 
			
		||||
        // Shutdown the main emulated process
 | 
			
		||||
        if (load_result == Core::SystemResultStatus::Success) {
 | 
			
		||||
            system.DetachDebugger();
 | 
			
		||||
            system.ShutdownMainProcess();
 | 
			
		||||
            detached_tasks.WaitForAllTasks();
 | 
			
		||||
            load_result = Core::SystemResultStatus::ErrorNotInitialized;
 | 
			
		||||
        if (m_load_result == Core::SystemResultStatus::Success) {
 | 
			
		||||
            m_system.DetachDebugger();
 | 
			
		||||
            m_system.ShutdownMainProcess();
 | 
			
		||||
            m_detached_tasks.WaitForAllTasks();
 | 
			
		||||
            m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Tear down the render window.
 | 
			
		||||
        window.reset();
 | 
			
		||||
        m_window.reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void PauseEmulation() {
 | 
			
		||||
        std::scoped_lock lock(m_mutex);
 | 
			
		||||
        m_system.Pause();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void UnPauseEmulation() {
 | 
			
		||||
        std::scoped_lock lock(m_mutex);
 | 
			
		||||
        m_system.Run();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void HaltEmulation() {
 | 
			
		||||
        std::scoped_lock lock(mutex);
 | 
			
		||||
        is_running = false;
 | 
			
		||||
        cv.notify_one();
 | 
			
		||||
        std::scoped_lock lock(m_mutex);
 | 
			
		||||
        m_is_running = false;
 | 
			
		||||
        m_cv.notify_one();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RunEmulation() {
 | 
			
		||||
        {
 | 
			
		||||
            std::scoped_lock lock(mutex);
 | 
			
		||||
            is_running = true;
 | 
			
		||||
            std::scoped_lock lock(m_mutex);
 | 
			
		||||
            m_is_running = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void(system.Run());
 | 
			
		||||
        void(m_system.Run());
 | 
			
		||||
 | 
			
		||||
        if (system.DebuggerEnabled()) {
 | 
			
		||||
            system.InitializeDebugger();
 | 
			
		||||
        if (m_system.DebuggerEnabled()) {
 | 
			
		||||
            m_system.InitializeDebugger();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        while (true) {
 | 
			
		||||
            {
 | 
			
		||||
                std::unique_lock lock(mutex);
 | 
			
		||||
                if (cv.wait_for(lock, std::chrono::milliseconds(100),
 | 
			
		||||
                                [&]() { return !is_running; })) {
 | 
			
		||||
                std::unique_lock lock(m_mutex);
 | 
			
		||||
                if (m_cv.wait_for(lock, std::chrono::milliseconds(100),
 | 
			
		||||
                                  [&]() { return !m_is_running; })) {
 | 
			
		||||
                    // Emulation halted.
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            {
 | 
			
		||||
                // Refresh performance stats.
 | 
			
		||||
                std::scoped_lock perf_stats_lock(perf_stats_mutex);
 | 
			
		||||
                perf_stats = system.GetAndResetPerfStats();
 | 
			
		||||
                std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
 | 
			
		||||
                m_perf_stats = m_system.GetAndResetPerfStats();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string GetRomTitle(const std::string& path) {
 | 
			
		||||
        return GetRomMetadata(path).title;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> GetRomIcon(const std::string& path) {
 | 
			
		||||
        return GetRomMetadata(path).icon;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ResetRomMetadata() {
 | 
			
		||||
        m_rom_metadata_cache.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct RomMetadata {
 | 
			
		||||
        std::string title;
 | 
			
		||||
        std::vector<u8> icon;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    RomMetadata GetRomMetadata(const std::string& path) {
 | 
			
		||||
        if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
 | 
			
		||||
            return search->second;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CacheRomMetadata(path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    RomMetadata CacheRomMetadata(const std::string& path) {
 | 
			
		||||
        const auto file = Core::GetGameFileFromPath(m_vfs, path);
 | 
			
		||||
        const auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
 | 
			
		||||
 | 
			
		||||
        RomMetadata entry;
 | 
			
		||||
        loader->ReadTitle(entry.title);
 | 
			
		||||
        loader->ReadIcon(entry.icon);
 | 
			
		||||
 | 
			
		||||
        m_rom_metadata_cache[path] = entry;
 | 
			
		||||
 | 
			
		||||
        return entry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    static EmulationSession s_instance;
 | 
			
		||||
 | 
			
		||||
    ANativeWindow* native_window{};
 | 
			
		||||
    // Frontend management
 | 
			
		||||
    std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
 | 
			
		||||
 | 
			
		||||
    InputCommon::InputSubsystem input_subsystem;
 | 
			
		||||
    Common::DetachedTasks detached_tasks;
 | 
			
		||||
    Core::System system;
 | 
			
		||||
    // Window management
 | 
			
		||||
    std::unique_ptr<EmuWindow_Android> m_window;
 | 
			
		||||
    ANativeWindow* m_native_window{};
 | 
			
		||||
 | 
			
		||||
    Core::PerfStatsResults perf_stats{};
 | 
			
		||||
    // Core emulation
 | 
			
		||||
    Core::System m_system;
 | 
			
		||||
    InputCommon::InputSubsystem m_input_subsystem;
 | 
			
		||||
    Common::DetachedTasks m_detached_tasks;
 | 
			
		||||
    Core::PerfStatsResults m_perf_stats{};
 | 
			
		||||
    std::shared_ptr<FileSys::RealVfsFilesystem> m_vfs;
 | 
			
		||||
    Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
 | 
			
		||||
    bool m_is_running{};
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<EmuWindow_Android> window;
 | 
			
		||||
    std::condition_variable_any cv;
 | 
			
		||||
    bool is_running{};
 | 
			
		||||
    Core::SystemResultStatus load_result{Core::SystemResultStatus::ErrorNotInitialized};
 | 
			
		||||
 | 
			
		||||
    mutable std::mutex perf_stats_mutex;
 | 
			
		||||
    mutable std::mutex mutex;
 | 
			
		||||
    // Synchronization
 | 
			
		||||
    std::condition_variable_any m_cv;
 | 
			
		||||
    mutable std::mutex m_perf_stats_mutex;
 | 
			
		||||
    mutable std::mutex m_mutex;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*static*/ EmulationSession EmulationSession::s_instance;
 | 
			
		||||
@@ -275,16 +334,25 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_ReloadKeys(JNIEnv* env,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_UnPauseEmulation([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                            [[maybe_unused]] jclass clazz) {}
 | 
			
		||||
                                                            [[maybe_unused]] jclass clazz) {
 | 
			
		||||
    EmulationSession::GetInstance().UnPauseEmulation();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                          [[maybe_unused]] jclass clazz) {}
 | 
			
		||||
                                                          [[maybe_unused]] jclass clazz) {
 | 
			
		||||
    EmulationSession::GetInstance().PauseEmulation();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                         [[maybe_unused]] jclass clazz) {
 | 
			
		||||
    EmulationSession::GetInstance().HaltEmulation();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                            [[maybe_unused]] jclass clazz) {
 | 
			
		||||
    EmulationSession::GetInstance().ResetRomMetadata();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                         [[maybe_unused]] jclass clazz) {
 | 
			
		||||
    return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
 | 
			
		||||
@@ -347,16 +415,21 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved([[maybe_unused]] JNIEnv*
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jintArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                        [[maybe_unused]] jclass clazz,
 | 
			
		||||
                                                        [[maybe_unused]] jstring j_file) {
 | 
			
		||||
    return {};
 | 
			
		||||
jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                         [[maybe_unused]] jclass clazz,
 | 
			
		||||
                                                         [[maybe_unused]] jstring j_filename) {
 | 
			
		||||
    auto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
 | 
			
		||||
    jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
 | 
			
		||||
    env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
 | 
			
		||||
                            reinterpret_cast<jbyte*>(icon_data.data()));
 | 
			
		||||
    return icon;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
                                                       [[maybe_unused]] jclass clazz,
 | 
			
		||||
                                                       [[maybe_unused]] jstring j_filename) {
 | 
			
		||||
    return env->NewStringUTF("");
 | 
			
		||||
    auto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
 | 
			
		||||
    return env->NewStringUTF(title.c_str());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDescription([[maybe_unused]] JNIEnv* env,
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,9 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_PauseEmulation(JNIE
 | 
			
		||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_StopEmulation(JNIEnv* env,
 | 
			
		||||
                                                                           jclass clazz);
 | 
			
		||||
 | 
			
		||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JNIEnv* env,
 | 
			
		||||
                                                                              jclass clazz);
 | 
			
		||||
 | 
			
		||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env,
 | 
			
		||||
                                                                           jclass clazz);
 | 
			
		||||
 | 
			
		||||
@@ -39,8 +42,9 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchEvent(JN
 | 
			
		||||
JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchMoved(JNIEnv* env, jclass clazz,
 | 
			
		||||
                                                                          jfloat x, jfloat y);
 | 
			
		||||
 | 
			
		||||
JNIEXPORT jintArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env, jclass clazz,
 | 
			
		||||
                                                                          jstring j_file);
 | 
			
		||||
JNIEXPORT jbyteArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetIcon(JNIEnv* env,
 | 
			
		||||
                                                                           jclass clazz,
 | 
			
		||||
                                                                           jstring j_file);
 | 
			
		||||
 | 
			
		||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_GetTitle(JNIEnv* env, jclass clazz,
 | 
			
		||||
                                                                         jstring j_filename);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,81 +1,76 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
<androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:background="?attr/selectableItemBackground"
 | 
			
		||||
    android:clickable="true"
 | 
			
		||||
    android:clipToPadding="false"
 | 
			
		||||
    android:focusable="true"
 | 
			
		||||
    android:foreground="?android:attr/selectableItemBackground"
 | 
			
		||||
    android:transitionName="card_game"
 | 
			
		||||
    tools:layout_width="match_parent">
 | 
			
		||||
    android:paddingStart="4dp"
 | 
			
		||||
    android:paddingTop="8dp"
 | 
			
		||||
    android:paddingEnd="4dp"
 | 
			
		||||
    android:paddingBottom="8dp"
 | 
			
		||||
    android:transitionName="card_game">
 | 
			
		||||
 | 
			
		||||
    <androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
        android:id="@+id/linearLayout"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:padding="8dp">
 | 
			
		||||
    <androidx.cardview.widget.CardView
 | 
			
		||||
        android:id="@+id/card_game_art"
 | 
			
		||||
        android:layout_width="150dp"
 | 
			
		||||
        android:layout_height="150dp"
 | 
			
		||||
        app:cardCornerRadius="4dp"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent">
 | 
			
		||||
 | 
			
		||||
        <ImageView
 | 
			
		||||
            android:id="@+id/image_game_screen"
 | 
			
		||||
            android:layout_width="56dp"
 | 
			
		||||
            android:layout_height="56dp"
 | 
			
		||||
            android:adjustViewBounds="false"
 | 
			
		||||
            android:cropToPadding="false"
 | 
			
		||||
            android:scaleType="fitCenter"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            tools:scaleType="fitCenter" />
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:layout_weight="1" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/text_game_title"
 | 
			
		||||
            android:id="@+id/text_game_title_inner"
 | 
			
		||||
            style="@android:style/TextAppearance.Material.Subhead"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="8dp"
 | 
			
		||||
            android:baselineAligned="false"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:gravity="center_vertical"
 | 
			
		||||
            android:lines="1"
 | 
			
		||||
            android:maxLines="1"
 | 
			
		||||
            android:textAlignment="viewStart"
 | 
			
		||||
            app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
            app:layout_constraintStart_toEndOf="@+id/image_game_screen"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            tools:text="The Legend of Zelda\nOcarina of Time 3D"
 | 
			
		||||
            android:textColor="@color/header_text" />
 | 
			
		||||
            android:gravity="center|top"
 | 
			
		||||
            android:maxLines="2"
 | 
			
		||||
            android:paddingLeft="2dp"
 | 
			
		||||
            android:paddingRight="2dp"
 | 
			
		||||
            android:paddingTop="8dp"
 | 
			
		||||
            android:visibility="visible"
 | 
			
		||||
            tools:text="The Legend of Zelda: The Wind Waker" />
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/text_company"
 | 
			
		||||
            style="@android:style/TextAppearance.Material.Caption"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:lines="1"
 | 
			
		||||
            android:maxLines="1"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="@+id/image_game_screen"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@+id/text_game_title"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/text_game_title"
 | 
			
		||||
            app:layout_constraintVertical_bias="0.842"
 | 
			
		||||
            tools:text="Nintendo"
 | 
			
		||||
            android:textColor="@color/header_subtext" />
 | 
			
		||||
    </androidx.cardview.widget.CardView>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/text_filename"
 | 
			
		||||
            style="@android:style/TextAppearance.Material.Caption"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:lines="1"
 | 
			
		||||
            android:maxLines="1"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="@+id/image_game_screen"
 | 
			
		||||
            app:layout_constraintStart_toStartOf="@+id/text_game_title"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/text_game_title"
 | 
			
		||||
            app:layout_constraintVertical_bias="0.0"
 | 
			
		||||
            tools:text="Pilotwings_Resort.cxi"
 | 
			
		||||
            android:textColor="@color/header_subtext" />
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/text_game_title"
 | 
			
		||||
        style="@android:style/TextAppearance.Material.Subhead"
 | 
			
		||||
        android:layout_width="150dp"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:ellipsize="end"
 | 
			
		||||
        android:maxLines="2"
 | 
			
		||||
        android:paddingTop="8dp"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="@+id/card_game_art"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="@+id/card_game_art"
 | 
			
		||||
        app:layout_constraintTop_toBottomOf="@+id/card_game_art"
 | 
			
		||||
        tools:text="The Legend of Zelda: The Wind Waker" />
 | 
			
		||||
 | 
			
		||||
    </androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/text_game_caption"
 | 
			
		||||
        style="@android:style/TextAppearance.Material.Caption"
 | 
			
		||||
        android:layout_width="150dp"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:ellipsize="end"
 | 
			
		||||
        android:lines="1"
 | 
			
		||||
        android:maxLines="1"
 | 
			
		||||
        android:paddingTop="8dp"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="@+id/card_game_art"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="@+id/card_game_art"
 | 
			
		||||
        app:layout_constraintTop_toBottomOf="@+id/text_game_title"
 | 
			
		||||
        tools:text="Nintendo" />
 | 
			
		||||
 | 
			
		||||
</androidx.cardview.widget.CardView>
 | 
			
		||||
</androidx.constraintlayout.widget.ConstraintLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,12 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent">
 | 
			
		||||
             xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
             android:layout_width="match_parent"
 | 
			
		||||
             android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 | 
			
		||||
        android:id="@+id/refresh_grid_games"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:id="@+id/swipe_refresh"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
        <RelativeLayout
 | 
			
		||||
@@ -22,12 +22,13 @@
 | 
			
		||||
                android:textSize="18sp"
 | 
			
		||||
                android:gravity="center" />
 | 
			
		||||
 | 
			
		||||
            <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
                android:id="@+id/grid_games"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                tools:listitem="@layout/card_game" />
 | 
			
		||||
        <androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/grid_games"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="match_parent"
 | 
			
		||||
            android:clipToPadding="false"
 | 
			
		||||
            tools:listitem="@layout/card_game" />
 | 
			
		||||
        </RelativeLayout>
 | 
			
		||||
 | 
			
		||||
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
    <dimen name="spacing_list">64dp</dimen>
 | 
			
		||||
    <dimen name="spacing_fab">72dp</dimen>
 | 
			
		||||
    <dimen name="menu_width">256dp</dimen>
 | 
			
		||||
    <dimen name="card_width">135dp</dimen>
 | 
			
		||||
    <dimen name="card_width">150dp</dimen>
 | 
			
		||||
 | 
			
		||||
    <dimen name="dialog_margin">20dp</dimen>
 | 
			
		||||
    <dimen name="elevated_app_bar">3dp</dimen>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user