summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2023-06-06 13:03:02 +0200
committerGitHub <noreply@github.com>2023-06-06 13:03:02 +0200
commit36ca747d552d7fd2bbd94abdd4db25e621cf3282 (patch)
tree576aff3597606758afc13059ddd855dfc90a6c6c /Source
parent3d4b4cfdc63b06420fad4976a19add23e33cdfe2 (diff)
parentf117e8a2f96ee5e5578407c38697cc20ed91a3cd (diff)
Merge pull request #11886 from t895/kotlin-ui
Android: Convert "ui" package to Kotlin
Diffstat (limited to 'Source')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.java27
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.kt21
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.java85
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.kt53
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java441
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.kt327
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java349
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt311
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java41
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.kt40
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.java9
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.kt10
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java418
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt370
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java58
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.kt36
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java147
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt122
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java32
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.kt31
20 files changed, 1321 insertions, 1607 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.java
deleted file mode 100644
index 4b4b763c2b..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2013 Dolphin Emulator Project
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-package org.dolphinemu.dolphinemu.ui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-
-/**
- * Work around a bug with the nVidia Shield.
- *
- * Without this View, the emulation SurfaceView acts like it has the
- * highest Z-value, blocking any other View, such as the menu fragments.
- */
-public final class NVidiaShieldWorkaroundView extends View
-{
- public NVidiaShieldWorkaroundView(Context context, AttributeSet attrs)
- {
- super(context, attrs);
-
- // Setting this seems to workaround the bug
- setWillNotDraw(false);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.kt
new file mode 100644
index 0000000000..16679e2c5f
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/NVidiaShieldWorkaroundView.kt
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+
+/**
+ * Work around a bug with the nVidia Shield.
+ *
+ *
+ * Without this View, the emulation SurfaceView acts like it has the
+ * highest Z-value, blocking any other View, such as the menu fragments.
+ */
+class NVidiaShieldWorkaroundView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+ init {
+ // Setting this seems to workaround the bug
+ setWillNotDraw(false)
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.java
deleted file mode 100644
index 08e4777303..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.main;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.leanback.widget.TitleViewAdapter;
-
-import org.dolphinemu.dolphinemu.R;
-
-public class CustomTitleView extends LinearLayout implements TitleViewAdapter.Provider
-{
- private final TextView mTitleView;
- private final View mBadgeView;
-
- private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter()
- {
- @Override
- public View getSearchAffordanceView()
- {
- return null;
- }
-
- @Override
- public void setTitle(CharSequence titleText)
- {
- CustomTitleView.this.setTitle(titleText);
- }
-
- @Override
- public void setBadgeDrawable(Drawable drawable)
- {
- CustomTitleView.this.setBadgeDrawable(drawable);
- }
- };
-
- public CustomTitleView(Context context)
- {
- this(context, null);
- }
-
- public CustomTitleView(Context context, AttributeSet attrs)
- {
- this(context, attrs, 0);
- }
-
- public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- View root = LayoutInflater.from(context).inflate(R.layout.tv_title, this);
- mTitleView = root.findViewById(R.id.title);
- mBadgeView = root.findViewById(R.id.badge);
- }
-
- public void setTitle(CharSequence title)
- {
- if (title != null)
- {
- mTitleView.setText(title);
- mTitleView.setVisibility(View.VISIBLE);
- mBadgeView.setVisibility(View.VISIBLE);
- }
- }
-
- public void setBadgeDrawable(Drawable drawable)
- {
- if (drawable != null)
- {
- mTitleView.setVisibility(View.GONE);
- mBadgeView.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- public TitleViewAdapter getTitleViewAdapter()
- {
- return mTitleViewAdapter;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.kt
new file mode 100644
index 0000000000..35f69195e9
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/CustomTitleView.kt
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.main
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.leanback.widget.TitleViewAdapter
+import org.dolphinemu.dolphinemu.R
+
+class CustomTitleView @JvmOverloads constructor(
+ context: Context?,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : LinearLayout(context, attrs, defStyle), TitleViewAdapter.Provider {
+ private val titleView: TextView
+ private val badgeView: View
+ private val titleViewAdapter: TitleViewAdapter = object : TitleViewAdapter() {
+ override fun getSearchAffordanceView(): View? = null
+
+ override fun setTitle(titleText: CharSequence?) = this@CustomTitleView.setTitle(titleText)
+
+ override fun setBadgeDrawable(drawable: Drawable?) =
+ this@CustomTitleView.setBadgeDrawable(drawable)
+ }
+
+ init {
+ val root = LayoutInflater.from(context).inflate(R.layout.tv_title, this)
+ titleView = root.findViewById(R.id.title)
+ badgeView = root.findViewById(R.id.badge)
+ }
+
+ fun setTitle(title: CharSequence?) {
+ if (title != null) {
+ titleView.text = title
+ titleView.visibility = VISIBLE
+ badgeView.visibility = VISIBLE
+ }
+ }
+
+ fun setBadgeDrawable(drawable: Drawable?) {
+ if (drawable != null) {
+ titleView.visibility = GONE
+ badgeView.visibility = VISIBLE
+ }
+ }
+
+ override fun getTitleViewAdapter(): TitleViewAdapter = titleViewAdapter
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
deleted file mode 100644
index 488f0909df..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
+++ /dev/null
@@ -1,441 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.main;
-
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.graphics.Insets;
-import androidx.core.splashscreen.SplashScreen;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowCompat;
-import androidx.core.view.WindowInsetsCompat;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-
-import com.google.android.material.color.MaterialColors;
-import com.google.android.material.tabs.TabLayout;
-
-import org.dolphinemu.dolphinemu.fragments.GridOptionDialogFragment;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter;
-import org.dolphinemu.dolphinemu.databinding.ActivityMainBinding;
-import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig;
-import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
-import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
-import org.dolphinemu.dolphinemu.utils.Action1;
-import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
-import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
-import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
-import org.dolphinemu.dolphinemu.utils.InsetsHelper;
-import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
-import org.dolphinemu.dolphinemu.utils.StartupHandler;
-import org.dolphinemu.dolphinemu.utils.ThemeHelper;
-import org.dolphinemu.dolphinemu.utils.WiiUtils;
-
-/**
- * The main Activity of the Lollipop style UI. Manages several PlatformGamesFragments, which
- * individually display a grid of available games for each Fragment, in a tabbed layout.
- */
-public final class MainActivity extends AppCompatActivity
- implements MainView, SwipeRefreshLayout.OnRefreshListener, ThemeProvider
-{
- private int mThemeId;
-
- private final MainPresenter mPresenter = new MainPresenter(this, this);
-
- private ActivityMainBinding mBinding;
-
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
- splashScreen.setKeepOnScreenCondition(
- () -> !DirectoryInitialization.areDolphinDirectoriesReady());
-
- ThemeHelper.setTheme(this);
-
- super.onCreate(savedInstanceState);
-
- mBinding = ActivityMainBinding.inflate(getLayoutInflater());
- setContentView(mBinding.getRoot());
-
- WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
- setInsets();
- ThemeHelper.enableStatusBarScrollTint(this, mBinding.appbarMain);
-
- mBinding.toolbarMain.setTitle(R.string.app_name);
- setSupportActionBar(mBinding.toolbarMain);
-
- // Set up the FAB.
- mBinding.buttonAddDirectory.setOnClickListener(view -> mPresenter.onFabClick());
- mBinding.appbarMain.addOnOffsetChangedListener((appBarLayout, verticalOffset) ->
- {
- if (verticalOffset == 0)
- {
- mBinding.buttonAddDirectory.extend();
- }
- else if (appBarLayout.getTotalScrollRange() == -verticalOffset)
- {
- mBinding.buttonAddDirectory.shrink();
- }
- });
-
- mPresenter.onCreate();
-
- // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
- if (savedInstanceState == null)
- {
- StartupHandler.HandleInit(this);
- new AfterDirectoryInitializationRunner().runWithLifecycle(this, this::checkTheme);
- }
-
- if (!DirectoryInitialization.isWaitingForWriteAccess(this))
- {
- new AfterDirectoryInitializationRunner()
- .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService);
- }
- }
-
- @Override
- protected void onResume()
- {
- ThemeHelper.setCorrectTheme(this);
-
- super.onResume();
-
- if (DirectoryInitialization.shouldStart(this))
- {
- DirectoryInitialization.start(this);
- new AfterDirectoryInitializationRunner()
- .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService);
- }
-
- mPresenter.onResume();
- }
-
- @Override
- protected void onDestroy()
- {
- super.onDestroy();
- mPresenter.onDestroy();
- }
-
- @Override
- protected void onStart()
- {
- super.onStart();
- StartupHandler.checkSessionReset(this);
- }
-
- @Override
- protected void onStop()
- {
- super.onStop();
-
- if (isChangingConfigurations())
- {
- MainPresenter.skipRescanningLibrary();
- }
- else if (DirectoryInitialization.areDolphinDirectoriesReady())
- {
- // If the currently selected platform tab changed, save it to disk
- NativeConfig.save(NativeConfig.LAYER_BASE);
- }
-
- StartupHandler.setSessionTime(this);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu)
- {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.menu_game_grid, menu);
-
- if (WiiUtils.isSystemMenuInstalled())
- {
- int resId = WiiUtils.isSystemMenuvWii() ?
- R.string.grid_menu_load_vwii_system_menu_installed :
- R.string.grid_menu_load_wii_system_menu_installed;
-
- menu.findItem(R.id.menu_load_wii_system_menu).setTitle(
- getString(resId, WiiUtils.getSystemMenuVersion()));
- }
-
- return true;
- }
-
- /**
- * MainView
- */
-
- @Override
- public void setVersionString(String version)
- {
- mBinding.toolbarMain.setSubtitle(version);
- }
-
- @Override
- public void launchSettingsActivity(MenuTag menuTag)
- {
- SettingsActivity.launch(this, menuTag);
- }
-
- @Override
- public void launchFileListActivity()
- {
- if (DirectoryInitialization.preferOldFolderPicker(this))
- {
- FileBrowserHelper.openDirectoryPicker(this, FileBrowserHelper.GAME_EXTENSIONS);
- }
- else
- {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
- startActivityForResult(intent, MainPresenter.REQUEST_DIRECTORY);
- }
- }
-
- @Override
- public void launchOpenFileActivity(int requestCode)
- {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(intent, requestCode);
- }
-
- /**
- * @param requestCode An int describing whether the Activity that is returning did so successfully.
- * @param resultCode An int describing what Activity is giving us this callback.
- * @param result The information the returning Activity is providing us.
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent result)
- {
- super.onActivityResult(requestCode, resultCode, result);
-
- // If the user picked a file, as opposed to just backing out.
- if (resultCode == RESULT_OK)
- {
- Uri uri = result.getData();
- switch (requestCode)
- {
- case MainPresenter.REQUEST_DIRECTORY:
- if (DirectoryInitialization.preferOldFolderPicker(this))
- {
- mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedPath(result));
- }
- else
- {
- mPresenter.onDirectorySelected(result);
- }
- break;
-
- case MainPresenter.REQUEST_GAME_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri,
- FileBrowserHelper.GAME_LIKE_EXTENSIONS,
- () -> EmulationActivity.launch(this, result.getData().toString(), false));
- break;
-
- case MainPresenter.REQUEST_WAD_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.WAD_EXTENSION,
- () -> mPresenter.installWAD(result.getData().toString()));
- break;
-
- case MainPresenter.REQUEST_WII_SAVE_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.BIN_EXTENSION,
- () -> mPresenter.importWiiSave(result.getData().toString()));
- break;
-
- case MainPresenter.REQUEST_NAND_BIN_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.BIN_EXTENSION,
- () -> mPresenter.importNANDBin(result.getData().toString()));
- break;
- }
- }
- else
- {
- MainPresenter.skipRescanningLibrary();
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults)
- {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-
- if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION)
- {
- if (grantResults[0] == PackageManager.PERMISSION_DENIED)
- {
- PermissionsHandler.setWritePermissionDenied();
- }
-
- DirectoryInitialization.start(this);
- new AfterDirectoryInitializationRunner()
- .runWithLifecycle(this, this::setPlatformTabsAndStartGameFileCacheService);
- }
- }
-
- /**
- * Called by the framework whenever any actionbar/toolbar icon is clicked.
- *
- * @param item The icon that was clicked on.
- * @return True if the event was handled, false to bubble it up to the OS.
- */
- @Override
- public boolean onOptionsItemSelected(MenuItem item)
- {
- return mPresenter.handleOptionSelection(item.getItemId(), this);
- }
-
- /**
- * Called when the user requests a refresh by swiping down.
- */
- @Override
- public void onRefresh()
- {
- setRefreshing(true);
- GameFileCacheManager.startRescan();
- }
-
- /**
- * Shows or hides the loading indicator.
- */
- @Override
- public void setRefreshing(boolean refreshing)
- {
- forEachPlatformGamesView(view -> view.setRefreshing(refreshing));
- }
-
- /**
- * To be called when the game file cache is updated.
- */
- @Override
- public void showGames()
- {
- forEachPlatformGamesView(PlatformGamesView::showGames);
- }
-
- @Override
- public void reloadGrid()
- {
- forEachPlatformGamesView(PlatformGamesView::refetchMetadata);
- }
-
- @Override
- public void showGridOptions()
- {
- new GridOptionDialogFragment().show(getSupportFragmentManager(), "gridOptions");
- }
-
- private void forEachPlatformGamesView(Action1<PlatformGamesView> action)
- {
- for (Platform platform : Platform.values())
- {
- PlatformGamesView fragment = getPlatformGamesView(platform);
- if (fragment != null)
- {
- action.call(fragment);
- }
- }
- }
-
- @Nullable
- private PlatformGamesView getPlatformGamesView(Platform platform)
- {
- String fragmentTag =
- "android:switcher:" + mBinding.pagerPlatforms.getId() + ":" + platform.toInt();
-
- return (PlatformGamesView) getSupportFragmentManager().findFragmentByTag(fragmentTag);
- }
-
- // Don't call this before DirectoryInitialization completes.
- private void setPlatformTabsAndStartGameFileCacheService()
- {
- PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
- getSupportFragmentManager(), this);
- mBinding.pagerPlatforms.setAdapter(platformPagerAdapter);
- mBinding.pagerPlatforms.setOffscreenPageLimit(platformPagerAdapter.getCount());
- mBinding.tabsPlatforms.setupWithViewPager(mBinding.pagerPlatforms);
- mBinding.tabsPlatforms.addOnTabSelectedListener(
- new TabLayout.ViewPagerOnTabSelectedListener(mBinding.pagerPlatforms)
- {
- @Override
- public void onTabSelected(@NonNull TabLayout.Tab tab)
- {
- super.onTabSelected(tab);
- IntSetting.MAIN_LAST_PLATFORM_TAB.setInt(NativeConfig.LAYER_BASE,
- tab.getPosition());
- }
- });
-
- for (int i = 0; i < PlatformPagerAdapter.TAB_ICONS.length; i++)
- {
- mBinding.tabsPlatforms.getTabAt(i).setIcon(PlatformPagerAdapter.TAB_ICONS[i]);
- }
-
- mBinding.pagerPlatforms.setCurrentItem(IntSetting.MAIN_LAST_PLATFORM_TAB.getInt());
-
- showGames();
- GameFileCacheManager.startLoad();
- }
-
- @Override
- public void setTheme(int themeId)
- {
- super.setTheme(themeId);
- this.mThemeId = themeId;
- }
-
- @Override
- public int getThemeId()
- {
- return mThemeId;
- }
-
- private void checkTheme()
- {
- ThemeHelper.setCorrectTheme(this);
- }
-
- private void setInsets()
- {
- ViewCompat.setOnApplyWindowInsetsListener(mBinding.appbarMain, (v, windowInsets) ->
- {
- Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
-
- InsetsHelper.insetAppBar(insets, mBinding.appbarMain);
-
- ViewGroup.MarginLayoutParams mlpFab =
- (ViewGroup.MarginLayoutParams) mBinding.buttonAddDirectory.getLayoutParams();
- int fabPadding = getResources().getDimensionPixelSize(R.dimen.spacing_large);
- mlpFab.leftMargin = insets.left + fabPadding;
- mlpFab.bottomMargin = insets.bottom + fabPadding;
- mlpFab.rightMargin = insets.right + fabPadding;
- mBinding.buttonAddDirectory.setLayoutParams(mlpFab);
-
- mBinding.pagerPlatforms.setPadding(insets.left, 0, insets.right, 0);
-
- InsetsHelper.applyNavbarWorkaround(insets.bottom, mBinding.workaroundView);
- ThemeHelper.setNavigationBarColor(this,
- MaterialColors.getColor(mBinding.appbarMain, R.attr.colorSurface));
-
- return windowInsets;
- });
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.kt
new file mode 100644
index 0000000000..84d870c921
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.kt
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.main
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup.MarginLayoutParams
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
+import com.google.android.material.appbar.AppBarLayout
+import com.google.android.material.color.MaterialColors
+import com.google.android.material.tabs.TabLayout
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter
+import org.dolphinemu.dolphinemu.databinding.ActivityMainBinding
+import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
+import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
+import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity
+import org.dolphinemu.dolphinemu.fragments.GridOptionDialogFragment
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.ui.platform.Platform
+import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView
+import org.dolphinemu.dolphinemu.utils.Action1
+import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
+import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
+import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
+import org.dolphinemu.dolphinemu.utils.InsetsHelper
+import org.dolphinemu.dolphinemu.utils.PermissionsHandler
+import org.dolphinemu.dolphinemu.utils.StartupHandler
+import org.dolphinemu.dolphinemu.utils.ThemeHelper
+import org.dolphinemu.dolphinemu.utils.WiiUtils
+
+class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProvider {
+ override var themeId = 0
+
+ private val presenter = MainPresenter(this, this)
+
+ private lateinit var binding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen().setKeepOnScreenCondition { !DirectoryInitialization.areDolphinDirectoriesReady() }
+
+ ThemeHelper.setTheme(this)
+
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ setInsets()
+ ThemeHelper.enableStatusBarScrollTint(this, binding.appbarMain)
+
+ binding.toolbarMain.setTitle(R.string.app_name)
+ setSupportActionBar(binding.toolbarMain)
+
+ // Set up the FAB.
+ binding.buttonAddDirectory.setOnClickListener { presenter.onFabClick() }
+ binding.appbarMain.addOnOffsetChangedListener { appBarLayout: AppBarLayout, verticalOffset: Int ->
+ if (verticalOffset == 0) {
+ binding.buttonAddDirectory.extend()
+ } else if (appBarLayout.totalScrollRange == -verticalOffset) {
+ binding.buttonAddDirectory.shrink()
+ }
+ }
+
+ presenter.onCreate()
+
+ // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
+ if (savedInstanceState == null) {
+ StartupHandler.HandleInit(this)
+ AfterDirectoryInitializationRunner().runWithLifecycle(this) {
+ ThemeHelper.setCorrectTheme(this)
+ }
+ }
+ if (!DirectoryInitialization.isWaitingForWriteAccess(this)) {
+ AfterDirectoryInitializationRunner()
+ .runWithLifecycle(this) { setPlatformTabsAndStartGameFileCacheService() }
+ }
+ }
+
+ override fun onResume() {
+ ThemeHelper.setCorrectTheme(this)
+
+ super.onResume()
+ if (DirectoryInitialization.shouldStart(this)) {
+ DirectoryInitialization.start(this)
+ AfterDirectoryInitializationRunner()
+ .runWithLifecycle(this) { setPlatformTabsAndStartGameFileCacheService() }
+ }
+
+ presenter.onResume()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ StartupHandler.checkSessionReset(this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (isChangingConfigurations) {
+ MainPresenter.skipRescanningLibrary()
+ } else if (DirectoryInitialization.areDolphinDirectoriesReady()) {
+ // If the currently selected platform tab changed, save it to disk
+ NativeConfig.save(NativeConfig.LAYER_BASE)
+ }
+
+ StartupHandler.setSessionTime(this)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_game_grid, menu)
+ if (WiiUtils.isSystemMenuInstalled()) {
+ val resId =
+ if (WiiUtils.isSystemMenuvWii()) R.string.grid_menu_load_vwii_system_menu_installed else R.string.grid_menu_load_wii_system_menu_installed
+ menu.findItem(R.id.menu_load_wii_system_menu).title =
+ getString(resId, WiiUtils.getSystemMenuVersion())
+ }
+ return true
+ }
+
+ /**
+ * MainView
+ */
+ override fun setVersionString(version: String) {
+ binding.toolbarMain.subtitle = version
+ }
+
+ override fun launchSettingsActivity(menuTag: MenuTag?) {
+ SettingsActivity.launch(this, menuTag)
+ }
+
+ override fun launchFileListActivity() {
+ if (DirectoryInitialization.preferOldFolderPicker(this)) {
+ FileBrowserHelper.openDirectoryPicker(this, FileBrowserHelper.GAME_EXTENSIONS)
+ } else {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ startActivityForResult(intent, MainPresenter.REQUEST_DIRECTORY)
+ }
+ }
+
+ override fun launchOpenFileActivity(requestCode: Int) {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "*/*"
+ startActivityForResult(intent, requestCode)
+ }
+
+ /**
+ * @param requestCode An int describing whether the Activity that is returning did so successfully.
+ * @param resultCode An int describing what Activity is giving us this callback.
+ * @param result The information the returning Activity is providing us.
+ */
+ override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
+ super.onActivityResult(requestCode, resultCode, result)
+
+ // If the user picked a file, as opposed to just backing out.
+ if (resultCode == RESULT_OK) {
+ val uri = result!!.data
+ when (requestCode) {
+ MainPresenter.REQUEST_DIRECTORY -> {
+ if (DirectoryInitialization.preferOldFolderPicker(this)) {
+ presenter.onDirectorySelected(FileBrowserHelper.getSelectedPath(result))
+ } else {
+ presenter.onDirectorySelected(result)
+ }
+ }
+
+ MainPresenter.REQUEST_GAME_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.GAME_LIKE_EXTENSIONS
+ ) { EmulationActivity.launch(this, result.data.toString(), false) }
+
+ MainPresenter.REQUEST_WAD_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.WAD_EXTENSION
+ ) { presenter.installWAD(result.data.toString()) }
+
+ MainPresenter.REQUEST_WII_SAVE_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.BIN_EXTENSION
+ ) { presenter.importWiiSave(result.data.toString()) }
+
+ MainPresenter.REQUEST_NAND_BIN_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.BIN_EXTENSION
+ ) { presenter.importNANDBin(result.data.toString()) }
+ }
+ } else {
+ MainPresenter.skipRescanningLibrary()
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
+ if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ PermissionsHandler.setWritePermissionDenied()
+ }
+
+ DirectoryInitialization.start(this)
+ AfterDirectoryInitializationRunner()
+ .runWithLifecycle(this) { setPlatformTabsAndStartGameFileCacheService() }
+ }
+ }
+
+ /**
+ * Called by the framework whenever any actionbar/toolbar icon is clicked.
+ *
+ * @param item The icon that was clicked on.
+ * @return True if the event was handled, false to bubble it up to the OS.
+ */
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return presenter.handleOptionSelection(item.itemId, this)
+ }
+
+ /**
+ * Called when the user requests a refresh by swiping down.
+ */
+ override fun onRefresh() {
+ setRefreshing(true)
+ GameFileCacheManager.startRescan()
+ }
+
+ /**
+ * Shows or hides the loading indicator.
+ */
+ override fun setRefreshing(refreshing: Boolean) =
+ forEachPlatformGamesView { view: PlatformGamesView -> view.setRefreshing(refreshing) }
+
+ /**
+ * To be called when the game file cache is updated.
+ */
+ override fun showGames() =
+ forEachPlatformGamesView { obj: PlatformGamesView -> obj.showGames() }
+
+ override fun reloadGrid() {
+ forEachPlatformGamesView { obj: PlatformGamesView -> obj.refetchMetadata() }
+ }
+
+ override fun showGridOptions() =
+ GridOptionDialogFragment().show(supportFragmentManager, "gridOptions")
+
+ private fun forEachPlatformGamesView(action: Action1<PlatformGamesView>) {
+ for (platform in Platform.values()) {
+ val fragment = getPlatformGamesView(platform)
+ if (fragment != null) {
+ action.call(fragment)
+ }
+ }
+ }
+
+ private fun getPlatformGamesView(platform: Platform): PlatformGamesView? {
+ val fragmentTag =
+ "android:switcher:" + binding.pagerPlatforms.id + ":" + platform.toInt()
+ return supportFragmentManager.findFragmentByTag(fragmentTag) as PlatformGamesView?
+ }
+
+ // Don't call this before DirectoryInitialization completes.
+ private fun setPlatformTabsAndStartGameFileCacheService() {
+ val platformPagerAdapter = PlatformPagerAdapter(
+ supportFragmentManager, this
+ )
+ binding.pagerPlatforms.adapter = platformPagerAdapter
+ binding.pagerPlatforms.offscreenPageLimit = platformPagerAdapter.count
+ binding.tabsPlatforms.setupWithViewPager(binding.pagerPlatforms)
+ binding.tabsPlatforms.addOnTabSelectedListener(
+ object : TabLayout.ViewPagerOnTabSelectedListener(binding.pagerPlatforms) {
+ override fun onTabSelected(tab: TabLayout.Tab) {
+ super.onTabSelected(tab)
+ IntSetting.MAIN_LAST_PLATFORM_TAB.setInt(
+ NativeConfig.LAYER_BASE,
+ tab.position
+ )
+ }
+ })
+
+ for (i in PlatformPagerAdapter.TAB_ICONS.indices) {
+ binding.tabsPlatforms.getTabAt(i)?.setIcon(PlatformPagerAdapter.TAB_ICONS[i])
+ }
+
+ binding.pagerPlatforms.currentItem = IntSetting.MAIN_LAST_PLATFORM_TAB.int
+
+ showGames()
+ GameFileCacheManager.startLoad()
+ }
+
+ override fun setTheme(themeId: Int) {
+ super.setTheme(themeId)
+ this.themeId = themeId
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(binding.appbarMain) { _: View?, windowInsets: WindowInsetsCompat ->
+ val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+
+ InsetsHelper.insetAppBar(insets, binding.appbarMain)
+
+ val mlpFab = binding.buttonAddDirectory.layoutParams as MarginLayoutParams
+ val fabPadding = resources.getDimensionPixelSize(R.dimen.spacing_large)
+ mlpFab.leftMargin = insets.left + fabPadding
+ mlpFab.bottomMargin = insets.bottom + fabPadding
+ mlpFab.rightMargin = insets.right + fabPadding
+ binding.buttonAddDirectory.layoutParams = mlpFab
+
+ binding.pagerPlatforms.setPadding(insets.left, 0, insets.right, 0)
+
+ InsetsHelper.applyNavbarWorkaround(insets.bottom, binding.workaroundView)
+ ThemeHelper.setNavigationBarColor(
+ this,
+ MaterialColors.getColor(binding.appbarMain, R.attr.colorSurface)
+ )
+
+ windowInsets
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java
deleted file mode 100644
index e136864faa..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java
+++ /dev/null
@@ -1,349 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.main;
-
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.net.Uri;
-
-import androidx.activity.ComponentActivity;
-import androidx.fragment.app.FragmentActivity;
-import androidx.lifecycle.Observer;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-
-import org.dolphinemu.dolphinemu.BuildConfig;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
-import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateProgressBarDialogFragment;
-import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment;
-import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel;
-import org.dolphinemu.dolphinemu.fragments.AboutDialogFragment;
-import org.dolphinemu.dolphinemu.model.GameFileCache;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
-import org.dolphinemu.dolphinemu.utils.BooleanSupplier;
-import org.dolphinemu.dolphinemu.utils.CompletableFuture;
-import org.dolphinemu.dolphinemu.utils.ContentHandler;
-import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
-import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
-import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
-import org.dolphinemu.dolphinemu.utils.ThreadUtil;
-import org.dolphinemu.dolphinemu.utils.WiiUtils;
-
-import java.util.Arrays;
-import java.util.concurrent.ExecutionException;
-
-public final class MainPresenter
-{
- public static final int REQUEST_DIRECTORY = 1;
- public static final int REQUEST_GAME_FILE = 2;
- public static final int REQUEST_SD_FILE = 3;
- public static final int REQUEST_WAD_FILE = 4;
- public static final int REQUEST_WII_SAVE_FILE = 5;
- public static final int REQUEST_NAND_BIN_FILE = 6;
-
- private static boolean sShouldRescanLibrary = true;
-
- private final MainView mView;
- private final FragmentActivity mActivity;
- private String mDirToAdd;
-
- public MainPresenter(MainView view, FragmentActivity activity)
- {
- mView = view;
- mActivity = activity;
- }
-
- public void onCreate()
- {
- // Ask the user to grant write permission if relevant and not already granted
- if (DirectoryInitialization.isWaitingForWriteAccess(mActivity))
- PermissionsHandler.requestWritePermission(mActivity);
-
- String versionName = BuildConfig.VERSION_NAME;
- mView.setVersionString(versionName);
-
- GameFileCacheManager.getGameFiles().observe(mActivity, (gameFiles) -> mView.showGames());
-
- Observer<Boolean> refreshObserver = (isLoading) ->
- {
- mView.setRefreshing(GameFileCacheManager.isLoadingOrRescanning());
- };
- GameFileCacheManager.isLoading().observe(mActivity, refreshObserver);
- GameFileCacheManager.isRescanning().observe(mActivity, refreshObserver);
- }
-
- public void onDestroy()
- {
- }
-
- public void onFabClick()
- {
- new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity,
- mView::launchFileListActivity);
- }
-
- public boolean handleOptionSelection(int itemId, ComponentActivity activity)
- {
- switch (itemId)
- {
- case R.id.menu_settings:
- mView.launchSettingsActivity(MenuTag.SETTINGS);
- return true;
-
- case R.id.menu_grid_options:
- mView.showGridOptions();
- return true;
-
- case R.id.menu_refresh:
- mView.setRefreshing(true);
- GameFileCacheManager.startRescan();
- return true;
-
- case R.id.button_add_directory:
- new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
- mView::launchFileListActivity);
- return true;
-
- case R.id.menu_open_file:
- mView.launchOpenFileActivity(REQUEST_GAME_FILE);
- return true;
-
- case R.id.menu_load_wii_system_menu:
- launchWiiSystemMenu();
- return true;
-
- case R.id.menu_online_system_update:
- new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
- this::launchOnlineUpdate);
- return true;
-
- case R.id.menu_install_wad:
- new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
- () -> mView.launchOpenFileActivity(REQUEST_WAD_FILE));
- return true;
-
- case R.id.menu_import_wii_save:
- new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
- () -> mView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE));
- return true;
-
- case R.id.menu_import_nand_backup:
- new AfterDirectoryInitializationRunner().runWithLifecycle(activity,
- () -> mView.launchOpenFileActivity(REQUEST_NAND_BIN_FILE));
- return true;
-
- case R.id.menu_about:
- showAboutDialog();
- }
-
- return false;
- }
-
- public void onResume()
- {
- if (mDirToAdd != null)
- {
- GameFileCache.addGameFolder(mDirToAdd);
- mDirToAdd = null;
- }
-
- if (sShouldRescanLibrary)
- {
- GameFileCacheManager.startRescan();
- }
-
- sShouldRescanLibrary = true;
- }
-
- /**
- * Called when a selection is made using the legacy folder picker.
- */
- public void onDirectorySelected(String dir)
- {
- mDirToAdd = dir;
- }
-
- /**
- * Called when a selection is made using the Storage Access Framework folder picker.
- */
- public void onDirectorySelected(Intent result)
- {
- Uri uri = result.getData();
-
- boolean recursive = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean();
- String[] childNames = ContentHandler.getChildNames(uri, recursive);
- if (Arrays.stream(childNames).noneMatch((name) -> FileBrowserHelper.GAME_EXTENSIONS.contains(
- FileBrowserHelper.getExtension(name, false))))
- {
- new MaterialAlertDialogBuilder(mActivity)
- .setMessage(mActivity.getString(R.string.wrong_file_extension_in_directory,
- FileBrowserHelper.setToSortedDelimitedString(
- FileBrowserHelper.GAME_EXTENSIONS)))
- .setPositiveButton(R.string.ok, null)
- .show();
- }
-
- ContentResolver contentResolver = mActivity.getContentResolver();
- Uri canonicalizedUri = contentResolver.canonicalize(uri);
- if (canonicalizedUri != null)
- uri = canonicalizedUri;
-
- int takeFlags = result.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
- mActivity.getContentResolver().takePersistableUriPermission(uri, takeFlags);
-
- mDirToAdd = uri.toString();
- }
-
- public void installWAD(String path)
- {
- ThreadUtil.runOnThreadAndShowResult(mActivity, R.string.import_in_progress, 0, () ->
- {
- boolean success = WiiUtils.installWAD(path);
- int message = success ? R.string.wad_install_success : R.string.wad_install_failure;
- return mActivity.getResources().getString(message);
- });
- }
-
- public void importWiiSave(String path)
- {
- CompletableFuture<Boolean> canOverwriteFuture = new CompletableFuture<>();
-
- ThreadUtil.runOnThreadAndShowResult(mActivity, R.string.import_in_progress, 0, () ->
- {
- BooleanSupplier canOverwrite = () ->
- {
- mActivity.runOnUiThread(() ->
- {
- new MaterialAlertDialogBuilder(mActivity)
- .setMessage(R.string.wii_save_exists)
- .setCancelable(false)
- .setPositiveButton(R.string.yes, (dialog, i) -> canOverwriteFuture.complete(true))
- .setNegativeButton(R.string.no, (dialog, i) -> canOverwriteFuture.complete(false))
- .show();
- });
-
- try
- {
- return canOverwriteFuture.get();
- }
- catch (ExecutionException | InterruptedException e)
- {
- // Shouldn't happen
- throw new RuntimeException(e);
- }
- };
-
- int result = WiiUtils.importWiiSave(path, canOverwrite);
-
- int message;
- switch (result)
- {
- case WiiUtils.RESULT_SUCCESS:
- message = R.string.wii_save_import_success;
- break;
- case WiiUtils.RESULT_CORRUPTED_SOURCE:
- message = R.string.wii_save_import_corruped_source;
- break;
- case WiiUtils.RESULT_TITLE_MISSING:
- message = R.string.wii_save_import_title_missing;
- break;
- case WiiUtils.RESULT_CANCELLED:
- return null;
- default:
- message = R.string.wii_save_import_error;
- break;
- }
- return mActivity.getResources().getString(message);
- });
- }
-
- public void importNANDBin(String path)
- {
- new MaterialAlertDialogBuilder(mActivity)
- .setMessage(R.string.nand_import_warning)
- .setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss())
- .setPositiveButton(R.string.yes, (dialog, i) ->
- {
- dialog.dismiss();
-
- ThreadUtil.runOnThreadAndShowResult(mActivity, R.string.import_in_progress,
- R.string.do_not_close_app, () ->
- {
- // ImportNANDBin unfortunately doesn't provide any result value...
- // It does however show a panic alert if something goes wrong.
- WiiUtils.importNANDBin(path);
- return null;
- });
- })
- .show();
- }
-
- public static void skipRescanningLibrary()
- {
- sShouldRescanLibrary = false;
- }
-
- private void launchOnlineUpdate()
- {
- if (WiiUtils.isSystemMenuInstalled())
- {
- SystemUpdateViewModel viewModel =
- new ViewModelProvider(mActivity).get(SystemUpdateViewModel.class);
- viewModel.setRegion(-1);
- launchUpdateProgressBarFragment(mActivity);
- }
- else
- {
- SystemMenuNotInstalledDialogFragment dialogFragment =
- new SystemMenuNotInstalledDialogFragment();
- dialogFragment
- .show(mActivity.getSupportFragmentManager(), "SystemMenuNotInstalledDialogFragment");
- }
- }
-
- public static void launchDiscUpdate(String path, FragmentActivity activity)
- {
- SystemUpdateViewModel viewModel =
- new ViewModelProvider(activity).get(SystemUpdateViewModel.class);
- viewModel.setDiscPath(path);
- launchUpdateProgressBarFragment(activity);
- }
-
- private static void launchUpdateProgressBarFragment(FragmentActivity activity)
- {
- SystemUpdateProgressBarDialogFragment progressBarFragment =
- new SystemUpdateProgressBarDialogFragment();
- progressBarFragment
- .show(activity.getSupportFragmentManager(), SystemUpdateProgressBarDialogFragment.TAG);
- progressBarFragment.setCancelable(false);
- }
-
- private void launchWiiSystemMenu()
- {
- new AfterDirectoryInitializationRunner().runWithLifecycle(mActivity, () ->
- {
- if (WiiUtils.isSystemMenuInstalled())
- {
- EmulationActivity.launchSystemMenu(mActivity);
- }
- else
- {
- SystemMenuNotInstalledDialogFragment dialogFragment =
- new SystemMenuNotInstalledDialogFragment();
- dialogFragment
- .show(mActivity.getSupportFragmentManager(),
- "SystemMenuNotInstalledDialogFragment");
- }
- });
- }
-
- private void showAboutDialog()
- {
- new AboutDialogFragment().show(mActivity.getSupportFragmentManager(), AboutDialogFragment.TAG);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
new file mode 100644
index 0000000000..ddd2bcd2d0
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.main
+
+import android.content.DialogInterface
+import android.content.Intent
+import androidx.activity.ComponentActivity
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModelProvider
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.dolphinemu.dolphinemu.BuildConfig
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
+import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemMenuNotInstalledDialogFragment
+import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateProgressBarDialogFragment
+import org.dolphinemu.dolphinemu.features.sysupdate.ui.SystemUpdateViewModel
+import org.dolphinemu.dolphinemu.fragments.AboutDialogFragment
+import org.dolphinemu.dolphinemu.model.GameFileCache
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
+import org.dolphinemu.dolphinemu.utils.BooleanSupplier
+import org.dolphinemu.dolphinemu.utils.CompletableFuture
+import org.dolphinemu.dolphinemu.utils.ContentHandler
+import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
+import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
+import org.dolphinemu.dolphinemu.utils.PermissionsHandler
+import org.dolphinemu.dolphinemu.utils.ThreadUtil
+import org.dolphinemu.dolphinemu.utils.WiiUtils
+import java.util.Arrays
+import java.util.concurrent.ExecutionException
+
+class MainPresenter(private val mainView: MainView, private val activity: FragmentActivity) {
+ private var dirToAdd: String? = null
+
+ fun onCreate() {
+ // Ask the user to grant write permission if relevant and not already granted
+ if (DirectoryInitialization.isWaitingForWriteAccess(activity))
+ PermissionsHandler.requestWritePermission(activity)
+
+ val versionName = BuildConfig.VERSION_NAME
+ mainView.setVersionString(versionName)
+
+ GameFileCacheManager.getGameFiles().observe(activity) { mainView.showGames() }
+ val refreshObserver =
+ Observer<Boolean> { _: Boolean? -> mainView.setRefreshing(GameFileCacheManager.isLoadingOrRescanning()) }
+ GameFileCacheManager.isLoading().observe(activity, refreshObserver)
+ GameFileCacheManager.isRescanning().observe(activity, refreshObserver)
+ }
+
+ fun onFabClick() {
+ AfterDirectoryInitializationRunner().runWithLifecycle(activity) { mainView.launchFileListActivity() }
+ }
+
+ fun handleOptionSelection(itemId: Int, activity: ComponentActivity): Boolean =
+ when (itemId) {
+ R.id.menu_settings -> {
+ mainView.launchSettingsActivity(MenuTag.SETTINGS)
+ true
+ }
+
+ R.id.menu_grid_options -> {
+ mainView.showGridOptions()
+ true
+ }
+
+ R.id.menu_refresh -> {
+ mainView.setRefreshing(true)
+ GameFileCacheManager.startRescan()
+ true
+ }
+
+ R.id.button_add_directory -> {
+ AfterDirectoryInitializationRunner().runWithLifecycle(activity) { mainView.launchFileListActivity() }
+ true
+ }
+
+ R.id.menu_open_file -> {
+ mainView.launchOpenFileActivity(REQUEST_GAME_FILE)
+ true
+ }
+
+ R.id.menu_load_wii_system_menu -> {
+ launchWiiSystemMenu()
+ true
+ }
+
+ R.id.menu_online_system_update -> {
+ AfterDirectoryInitializationRunner().runWithLifecycle(activity) { launchOnlineUpdate() }
+ true
+ }
+
+ R.id.menu_install_wad -> {
+ AfterDirectoryInitializationRunner().runWithLifecycle(
+ activity
+ ) { mainView.launchOpenFileActivity(REQUEST_WAD_FILE) }
+ true
+ }
+
+ R.id.menu_import_wii_save -> {
+ AfterDirectoryInitializationRunner().runWithLifecycle(
+ activity
+ ) { mainView.launchOpenFileActivity(REQUEST_WII_SAVE_FILE) }
+ true
+ }
+
+ R.id.menu_import_nand_backup -> {
+ AfterDirectoryInitializationRunner().runWithLifecycle(
+ activity
+ ) { mainView.launchOpenFileActivity(REQUEST_NAND_BIN_FILE) }
+ true
+ }
+
+ R.id.menu_about -> {
+ showAboutDialog()
+ false
+ }
+
+ else -> false
+ }
+
+ fun onResume() {
+ if (dirToAdd != null) {
+ GameFileCache.addGameFolder(dirToAdd)
+ dirToAdd = null
+ }
+
+ if (shouldRescanLibrary) {
+ GameFileCacheManager.startRescan()
+ }
+
+ shouldRescanLibrary = true
+ }
+
+ /**
+ * Called when a selection is made using the legacy folder picker.
+ */
+ fun onDirectorySelected(dir: String?) {
+ dirToAdd = dir
+ }
+
+ /**
+ * Called when a selection is made using the Storage Access Framework folder picker.
+ */
+ fun onDirectorySelected(result: Intent) {
+ var uri = result.data!!
+
+ val recursive = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean
+ val childNames = ContentHandler.getChildNames(uri, recursive)
+ if (Arrays.stream(childNames).noneMatch {
+ FileBrowserHelper.GAME_EXTENSIONS
+ .contains(FileBrowserHelper.getExtension(it, false))
+ }) {
+ MaterialAlertDialogBuilder(activity)
+ .setMessage(
+ activity.getString(
+ R.string.wrong_file_extension_in_directory,
+ FileBrowserHelper.setToSortedDelimitedString(FileBrowserHelper.GAME_EXTENSIONS)
+ )
+ )
+ .setPositiveButton(android.R.string.ok, null)
+ .show()
+ }
+
+ val contentResolver = activity.contentResolver
+ val canonicalizedUri = contentResolver.canonicalize(uri)
+ if (canonicalizedUri != null)
+ uri = canonicalizedUri
+
+ val takeFlags = result.flags and Intent.FLAG_GRANT_READ_URI_PERMISSION
+ activity.contentResolver.takePersistableUriPermission(uri, takeFlags)
+
+ dirToAdd = uri.toString()
+ }
+
+ fun installWAD(path: String?) {
+ ThreadUtil.runOnThreadAndShowResult(
+ activity,
+ R.string.import_in_progress,
+ 0,
+ {
+ val success = WiiUtils.installWAD(path!!)
+ val message =
+ if (success) R.string.wad_install_success else R.string.wad_install_failure
+ activity.getString(message)
+ })
+ }
+
+ fun importWiiSave(path: String?) {
+ val canOverwriteFuture = CompletableFuture<Boolean>()
+ ThreadUtil.runOnThreadAndShowResult(
+ activity,
+ R.string.import_in_progress,
+ 0,
+ {
+ val canOverwrite = BooleanSupplier {
+ activity.runOnUiThread {
+ MaterialAlertDialogBuilder(activity)
+ .setMessage(R.string.wii_save_exists)
+ .setCancelable(false)
+ .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int ->
+ canOverwriteFuture.complete(true)
+ }
+ .setNegativeButton(R.string.no) { _: DialogInterface?, _: Int ->
+ canOverwriteFuture.complete(false)
+ }
+ .show()
+ }
+ try {
+ return@BooleanSupplier canOverwriteFuture.get()
+ } catch (e: ExecutionException) {
+ // Shouldn't happen
+ throw RuntimeException(e)
+ } catch (e: InterruptedException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ val message: Int = when (WiiUtils.importWiiSave(path!!, canOverwrite)) {
+ WiiUtils.RESULT_SUCCESS -> R.string.wii_save_import_success
+ WiiUtils.RESULT_CORRUPTED_SOURCE -> R.string.wii_save_import_corruped_source
+ WiiUtils.RESULT_TITLE_MISSING -> R.string.wii_save_import_title_missing
+ WiiUtils.RESULT_CANCELLED -> return@runOnThreadAndShowResult null
+ else -> R.string.wii_save_import_error
+ }
+ activity.resources.getString(message)
+ })
+ }
+
+ fun importNANDBin(path: String?) {
+ MaterialAlertDialogBuilder(activity)
+ .setMessage(R.string.nand_import_warning)
+ .setNegativeButton(R.string.no) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
+ .setPositiveButton(R.string.yes) { dialog: DialogInterface, _: Int ->
+ dialog.dismiss()
+ ThreadUtil.runOnThreadAndShowResult(
+ activity,
+ R.string.import_in_progress,
+ R.string.do_not_close_app,
+ {
+ // ImportNANDBin unfortunately doesn't provide any result value...
+ // It does however show a panic alert if something goes wrong.
+ WiiUtils.importNANDBin(path!!)
+ null
+ })
+ }
+ .show()
+ }
+
+ private fun launchOnlineUpdate() {
+ if (WiiUtils.isSystemMenuInstalled()) {
+ val viewModel = ViewModelProvider(activity)[SystemUpdateViewModel::class.java]
+ viewModel.region = -1
+ launchUpdateProgressBarFragment(activity)
+ } else {
+ SystemMenuNotInstalledDialogFragment().show(
+ activity.supportFragmentManager,
+ SystemMenuNotInstalledDialogFragment.TAG
+ )
+ }
+ }
+
+ private fun launchWiiSystemMenu() {
+ AfterDirectoryInitializationRunner().runWithLifecycle(activity) {
+ if (WiiUtils.isSystemMenuInstalled()) {
+ EmulationActivity.launchSystemMenu(activity)
+ } else {
+ SystemMenuNotInstalledDialogFragment().show(
+ activity.supportFragmentManager,
+ SystemMenuNotInstalledDialogFragment.TAG
+ )
+ }
+ }
+ }
+
+ private fun showAboutDialog() {
+ AboutDialogFragment().show(activity.supportFragmentManager, AboutDialogFragment.TAG)
+ }
+
+ companion object {
+ const val REQUEST_DIRECTORY = 1
+ const val REQUEST_GAME_FILE = 2
+ const val REQUEST_SD_FILE = 3
+ const val REQUEST_WAD_FILE = 4
+ const val REQUEST_WII_SAVE_FILE = 5
+ const val REQUEST_NAND_BIN_FILE = 6
+
+ private var shouldRescanLibrary = true
+
+ @JvmStatic
+ fun skipRescanningLibrary() {
+ shouldRescanLibrary = false
+ }
+
+ @JvmStatic
+ fun launchDiscUpdate(path: String, activity: FragmentActivity) {
+ val viewModel = ViewModelProvider(activity)[SystemUpdateViewModel::class.java]
+ viewModel.discPath = path
+ launchUpdateProgressBarFragment(activity)
+ }
+
+ private fun launchUpdateProgressBarFragment(activity: FragmentActivity) {
+ val progressBarFragment = SystemUpdateProgressBarDialogFragment()
+ progressBarFragment
+ .show(activity.supportFragmentManager, SystemUpdateProgressBarDialogFragment.TAG)
+ progressBarFragment.isCancelable = false
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java
deleted file mode 100644
index 604cb5d1a8..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.main;
-
-import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
-
-/**
- * Abstraction for the screen that shows on application launch.
- * Implementations will differ primarily to target touch-screen
- * or non-touch screen devices.
- */
-public interface MainView
-{
- /**
- * Pass the view the native library's version string. Displaying
- * it is optional.
- *
- * @param version A string pulled from native code.
- */
- void setVersionString(String version);
-
- void launchSettingsActivity(MenuTag menuTag);
-
- void launchFileListActivity();
-
- void launchOpenFileActivity(int requestCode);
-
- /**
- * Shows or hides the loading indicator.
- */
- void setRefreshing(boolean refreshing);
-
- /**
- * To be called when the game file cache is updated.
- */
- void showGames();
-
- void reloadGrid();
-
- void showGridOptions();
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.kt
new file mode 100644
index 0000000000..3892ee7599
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.kt
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.main
+
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
+
+/**
+ * Abstraction for the screen that shows on application launch.
+ * Implementations will differ primarily to target touch-screen
+ * or non-touch screen devices.
+ */
+interface MainView {
+ /**
+ * Pass the view the native library's version string. Displaying
+ * it is optional.
+ *
+ * @param version A string pulled from native code.
+ */
+ fun setVersionString(version: String)
+
+ fun launchSettingsActivity(menuTag: MenuTag?)
+
+ fun launchFileListActivity()
+
+ fun launchOpenFileActivity(requestCode: Int)
+
+ /**
+ * Shows or hides the loading indicator.
+ */
+ fun setRefreshing(refreshing: Boolean)
+
+ /**
+ * To be called when the game file cache is updated.
+ */
+ fun showGames()
+
+ fun reloadGrid()
+
+ fun showGridOptions()
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.java
deleted file mode 100644
index 1be137f33e..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.dolphinemu.dolphinemu.ui.main;
-
-public interface ThemeProvider
-{
- /**
- * Provides theme ID by overriding an activity's 'setTheme' method and returning that result
- */
- int getThemeId();
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.kt
new file mode 100644
index 0000000000..b49011a987
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/ThemeProvider.kt
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.main
+
+interface ThemeProvider {
+ /**
+ * Provides theme ID by overriding an activity's 'setTheme' method and returning that result
+ */
+ val themeId: Int
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
deleted file mode 100644
index 2da1b8591d..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
+++ /dev/null
@@ -1,418 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.main;
-
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.core.splashscreen.SplashScreen;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-import androidx.leanback.app.BrowseSupportFragment;
-import androidx.leanback.widget.ArrayObjectAdapter;
-import androidx.leanback.widget.HeaderItem;
-import androidx.leanback.widget.ListRow;
-import androidx.leanback.widget.ListRowPresenter;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-
-import org.dolphinemu.dolphinemu.fragments.GridOptionDialogFragment;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.adapters.GameRowPresenter;
-import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter;
-import org.dolphinemu.dolphinemu.databinding.ActivityTvMainBinding;
-import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
-import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.model.TvSettingsItem;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
-import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
-import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
-import org.dolphinemu.dolphinemu.utils.StartupHandler;
-import org.dolphinemu.dolphinemu.utils.TvUtil;
-import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-public final class TvMainActivity extends FragmentActivity
- implements MainView, SwipeRefreshLayout.OnRefreshListener
-{
- private final MainPresenter mPresenter = new MainPresenter(this, this);
-
- private SwipeRefreshLayout mSwipeRefresh;
-
- private BrowseSupportFragment mBrowseFragment;
-
- private final ArrayList<ArrayObjectAdapter> mGameRows = new ArrayList<>();
-
- private ActivityTvMainBinding mBinding;
-
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
- splashScreen.setKeepOnScreenCondition(
- () -> !DirectoryInitialization.areDolphinDirectoriesReady());
-
- super.onCreate(savedInstanceState);
- mBinding = ActivityTvMainBinding.inflate(getLayoutInflater());
- setContentView(mBinding.getRoot());
-
- setupUI();
-
- mPresenter.onCreate();
-
- // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
- if (savedInstanceState == null)
- {
- StartupHandler.HandleInit(this);
- }
- }
-
- @Override
- protected void onResume()
- {
- super.onResume();
-
- if (DirectoryInitialization.shouldStart(this))
- {
- DirectoryInitialization.start(this);
- GameFileCacheManager.startLoad();
- }
-
- mPresenter.onResume();
- }
-
- @Override
- protected void onDestroy()
- {
- super.onDestroy();
- mPresenter.onDestroy();
- }
-
- @Override
- protected void onStart()
- {
- super.onStart();
- StartupHandler.checkSessionReset(this);
- }
-
- @Override
- protected void onStop()
- {
- super.onStop();
-
- if (isChangingConfigurations())
- {
- MainPresenter.skipRescanningLibrary();
- }
-
- StartupHandler.setSessionTime(this);
- }
-
- void setupUI()
- {
- mSwipeRefresh = mBinding.swipeRefresh;
-
- mSwipeRefresh.setOnRefreshListener(this);
-
- setRefreshing(GameFileCacheManager.isLoadingOrRescanning());
-
- final FragmentManager fragmentManager = getSupportFragmentManager();
- mBrowseFragment = new BrowseSupportFragment();
- fragmentManager
- .beginTransaction()
- .add(R.id.content, mBrowseFragment, "BrowseFragment")
- .commit();
-
- // Set display parameters for the BrowseFragment
- mBrowseFragment.setHeadersState(BrowseSupportFragment.HEADERS_ENABLED);
- mBrowseFragment.setBrandColor(ContextCompat.getColor(this, R.color.dolphin_blue));
- buildRowsAdapter();
-
- mBrowseFragment.setOnItemViewClickedListener(
- (itemViewHolder, item, rowViewHolder, row) ->
- {
- // Special case: user clicked on a settings row item.
- if (item instanceof TvSettingsItem)
- {
- TvSettingsItem settingsItem = (TvSettingsItem) item;
- mPresenter.handleOptionSelection(settingsItem.getItemId(), this);
- }
- else
- {
- TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder;
-
- // Start the emulation activity and send the path of the clicked ISO to it.
- String[] paths = GameFileCacheManager.findSecondDiscAndGetPaths(holder.gameFile);
- EmulationActivity.launch(TvMainActivity.this, paths, false);
- }
- });
- }
-
- /**
- * MainView
- */
-
- @Override
- public void setVersionString(String version)
- {
- mBrowseFragment.setTitle(version);
- }
-
- @Override
- public void launchSettingsActivity(MenuTag menuTag)
- {
- SettingsActivity.launch(this, menuTag);
- }
-
- @Override
- public void launchFileListActivity()
- {
- if (DirectoryInitialization.preferOldFolderPicker(this))
- {
- FileBrowserHelper.openDirectoryPicker(this, FileBrowserHelper.GAME_EXTENSIONS);
- }
- else
- {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
- startActivityForResult(intent, MainPresenter.REQUEST_DIRECTORY);
- }
- }
-
- @Override
- public void launchOpenFileActivity(int requestCode)
- {
- Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(intent, requestCode);
- }
-
- /**
- * Shows or hides the loading indicator.
- */
- @Override
- public void setRefreshing(boolean refreshing)
- {
- mSwipeRefresh.setRefreshing(refreshing);
- }
-
- @Override
- public void showGames()
- {
- // Kicks off the program services to update all channels
- TvUtil.updateAllChannels(getApplicationContext());
-
- buildRowsAdapter();
- }
-
- @Override
- public void reloadGrid()
- {
- for (ArrayObjectAdapter row : mGameRows)
- {
- row.notifyArrayItemRangeChanged(0, row.size());
- }
- }
-
- @Override
- public void showGridOptions()
- {
- new GridOptionDialogFragment().show(getSupportFragmentManager(), "gridOptions");
- }
-
- /**
- * Callback from AddDirectoryActivity. Applies any changes necessary to the GameGridActivity.
- *
- * @param requestCode An int describing whether the Activity that is returning did so successfully.
- * @param resultCode An int describing what Activity is giving us this callback.
- * @param result The information the returning Activity is providing us.
- */
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent result)
- {
- super.onActivityResult(requestCode, resultCode, result);
-
- // If the user picked a file, as opposed to just backing out.
- if (resultCode == RESULT_OK)
- {
- Uri uri = result.getData();
- switch (requestCode)
- {
- case MainPresenter.REQUEST_DIRECTORY:
- if (DirectoryInitialization.preferOldFolderPicker(this))
- {
- mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedPath(result));
- }
- else
- {
- mPresenter.onDirectorySelected(result);
- }
- break;
-
- case MainPresenter.REQUEST_GAME_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri,
- FileBrowserHelper.GAME_LIKE_EXTENSIONS,
- () -> EmulationActivity.launch(this, result.getData().toString(), false));
- break;
-
- case MainPresenter.REQUEST_WAD_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.WAD_EXTENSION,
- () -> mPresenter.installWAD(result.getData().toString()));
- break;
-
- case MainPresenter.REQUEST_WII_SAVE_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.BIN_EXTENSION,
- () -> mPresenter.importWiiSave(result.getData().toString()));
- break;
-
- case MainPresenter.REQUEST_NAND_BIN_FILE:
- FileBrowserHelper.runAfterExtensionCheck(this, uri, FileBrowserHelper.BIN_EXTENSION,
- () -> mPresenter.importNANDBin(result.getData().toString()));
- break;
- }
- }
- else
- {
- MainPresenter.skipRescanningLibrary();
- }
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
- @NonNull int[] grantResults)
- {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-
- if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION)
- {
- if (grantResults[0] == PackageManager.PERMISSION_DENIED)
- {
- PermissionsHandler.setWritePermissionDenied();
- }
-
- DirectoryInitialization.start(this);
- GameFileCacheManager.startLoad();
- }
- }
-
- /**
- * Called when the user requests a refresh by swiping down.
- */
- @Override
- public void onRefresh()
- {
- setRefreshing(true);
- GameFileCacheManager.startRescan();
- }
-
- private void buildRowsAdapter()
- {
- ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
- mGameRows.clear();
-
- if (!DirectoryInitialization.isWaitingForWriteAccess(this))
- {
- GameFileCacheManager.startLoad();
- }
-
- for (Platform platform : Platform.values())
- {
- ListRow row = buildGamesRow(platform, GameFileCacheManager.getGameFilesForPlatform(platform));
-
- // Add row to the adapter only if it is not empty.
- if (row != null)
- {
- rowsAdapter.add(row);
- }
- }
-
- rowsAdapter.add(buildSettingsRow());
-
- mBrowseFragment.setAdapter(rowsAdapter);
- }
-
- private ListRow buildGamesRow(Platform platform, Collection<GameFile> gameFiles)
- {
- // If there are no games, don't return a Row.
- if (gameFiles.size() == 0)
- {
- return null;
- }
-
- // Create an adapter for this row.
- ArrayObjectAdapter row = new ArrayObjectAdapter(new GameRowPresenter(this));
- row.addAll(0, gameFiles);
-
- // Keep a reference to the row in case we need to refresh it.
- mGameRows.add(row);
-
- // Create a header for this row.
- HeaderItem header = new HeaderItem(platform.toInt(), getString(platform.getHeaderName()));
-
- // Create the row, passing it the filled adapter and the header, and give it to the master adapter.
- return new ListRow(header, row);
- }
-
- private ListRow buildSettingsRow()
- {
- ArrayObjectAdapter rowItems = new ArrayObjectAdapter(new SettingsRowPresenter());
-
- rowItems.add(new TvSettingsItem(R.id.menu_settings,
- R.drawable.ic_settings_tv,
- R.string.grid_menu_settings));
-
- rowItems.add(new TvSettingsItem(R.id.button_add_directory,
- R.drawable.ic_add_tv,
- R.string.add_directory_title));
-
- rowItems.add(new TvSettingsItem(R.id.menu_grid_options,
- R.drawable.ic_list_tv,
- R.string.grid_menu_grid_options));
-
- rowItems.add(new TvSettingsItem(R.id.menu_refresh,
- R.drawable.ic_refresh_tv,
- R.string.grid_menu_refresh));
-
- rowItems.add(new TvSettingsItem(R.id.menu_open_file,
- R.drawable.ic_play_tv,
- R.string.grid_menu_open_file));
-
- rowItems.add(new TvSettingsItem(R.id.menu_install_wad,
- R.drawable.ic_folder_tv,
- R.string.grid_menu_install_wad));
-
- rowItems.add(new TvSettingsItem(R.id.menu_load_wii_system_menu,
- R.drawable.ic_folder_tv,
- R.string.grid_menu_load_wii_system_menu));
-
- rowItems.add(new TvSettingsItem(R.id.menu_import_wii_save,
- R.drawable.ic_folder_tv,
- R.string.grid_menu_import_wii_save));
-
- rowItems.add(new TvSettingsItem(R.id.menu_import_nand_backup,
- R.drawable.ic_folder_tv,
- R.string.grid_menu_import_nand_backup));
-
- rowItems.add(new TvSettingsItem(R.id.menu_online_system_update,
- R.drawable.ic_folder_tv,
- R.string.grid_menu_online_system_update));
-
- rowItems.add(new TvSettingsItem(R.id.menu_about,
- R.drawable.ic_info_tv,
- R.string.grid_menu_about));
-
- // Create a header for this row.
- HeaderItem header = new HeaderItem(R.string.settings, getString(R.string.settings));
-
- return new ListRow(header, rowItems);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt
new file mode 100644
index 0000000000..79ec2f2943
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.main
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Bundle
+import androidx.core.content.ContextCompat
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.fragment.app.FragmentActivity
+import androidx.leanback.app.BrowseSupportFragment
+import androidx.leanback.widget.ArrayObjectAdapter
+import androidx.leanback.widget.HeaderItem
+import androidx.leanback.widget.ListRow
+import androidx.leanback.widget.ListRowPresenter
+import androidx.leanback.widget.OnItemViewClickedListener
+import androidx.leanback.widget.Presenter
+import androidx.leanback.widget.Row
+import androidx.leanback.widget.RowPresenter
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+import org.dolphinemu.dolphinemu.adapters.GameRowPresenter
+import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter
+import org.dolphinemu.dolphinemu.databinding.ActivityTvMainBinding
+import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
+import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity
+import org.dolphinemu.dolphinemu.fragments.GridOptionDialogFragment
+import org.dolphinemu.dolphinemu.model.GameFile
+import org.dolphinemu.dolphinemu.model.TvSettingsItem
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.ui.platform.Platform
+import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
+import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
+import org.dolphinemu.dolphinemu.utils.PermissionsHandler
+import org.dolphinemu.dolphinemu.utils.StartupHandler
+import org.dolphinemu.dolphinemu.utils.TvUtil
+import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder
+
+class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
+ private val presenter = MainPresenter(this, this)
+
+ private var swipeRefresh: SwipeRefreshLayout? = null
+
+ private var browseFragment: BrowseSupportFragment? = null
+
+ private val gameRows = ArrayList<ArrayObjectAdapter>()
+
+ private lateinit var binding: ActivityTvMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen().setKeepOnScreenCondition { !DirectoryInitialization.areDolphinDirectoriesReady() }
+
+ super.onCreate(savedInstanceState)
+ binding = ActivityTvMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ setupUI()
+
+ presenter.onCreate()
+
+ // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
+ if (savedInstanceState == null) {
+ StartupHandler.HandleInit(this)
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (DirectoryInitialization.shouldStart(this)) {
+ DirectoryInitialization.start(this)
+ GameFileCacheManager.startLoad()
+ }
+
+ presenter.onResume()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ StartupHandler.checkSessionReset(this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (isChangingConfigurations) {
+ MainPresenter.skipRescanningLibrary()
+ }
+ StartupHandler.setSessionTime(this)
+ }
+
+ private fun setupUI() {
+ swipeRefresh = binding.swipeRefresh
+ swipeRefresh!!.setOnRefreshListener(this)
+ setRefreshing(GameFileCacheManager.isLoadingOrRescanning())
+
+ browseFragment = BrowseSupportFragment()
+ supportFragmentManager
+ .beginTransaction()
+ .add(R.id.content, browseFragment!!, "BrowseFragment")
+ .commit()
+
+ // Set display parameters for the BrowseFragment
+ browseFragment?.headersState = BrowseSupportFragment.HEADERS_ENABLED
+ browseFragment?.brandColor = ContextCompat.getColor(this, R.color.dolphin_blue)
+ buildRowsAdapter()
+
+ browseFragment?.onItemViewClickedListener =
+ OnItemViewClickedListener { itemViewHolder: Presenter.ViewHolder, item: Any?, _: RowPresenter.ViewHolder?, _: Row? ->
+ // Special case: user clicked on a settings row item.
+ if (item is TvSettingsItem) {
+ presenter.handleOptionSelection(item.itemId, this)
+ } else {
+ val holder = itemViewHolder as TvGameViewHolder
+
+ // Start the emulation activity and send the path of the clicked ISO to it.
+ val paths = GameFileCacheManager.findSecondDiscAndGetPaths(holder.gameFile)
+ EmulationActivity.launch(this@TvMainActivity, paths, false)
+ }
+ }
+ }
+
+ /**
+ * MainView
+ */
+ override fun setVersionString(version: String) {
+ browseFragment?.title = version
+ }
+
+ override fun launchSettingsActivity(menuTag: MenuTag?) {
+ SettingsActivity.launch(this, menuTag)
+ }
+
+ override fun launchFileListActivity() {
+ if (DirectoryInitialization.preferOldFolderPicker(this)) {
+ FileBrowserHelper.openDirectoryPicker(this, FileBrowserHelper.GAME_EXTENSIONS)
+ } else {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ startActivityForResult(intent, MainPresenter.REQUEST_DIRECTORY)
+ }
+ }
+
+ override fun launchOpenFileActivity(requestCode: Int) {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "*/*"
+ startActivityForResult(intent, requestCode)
+ }
+
+ /**
+ * Shows or hides the loading indicator.
+ */
+ override fun setRefreshing(refreshing: Boolean) {
+ swipeRefresh?.isRefreshing = refreshing
+ }
+
+ override fun showGames() {
+ // Kicks off the program services to update all channels
+ TvUtil.updateAllChannels(applicationContext)
+
+ buildRowsAdapter()
+ }
+
+ override fun reloadGrid() {
+ for (row in gameRows) {
+ row.notifyArrayItemRangeChanged(0, row.size())
+ }
+ }
+
+ override fun showGridOptions() {
+ GridOptionDialogFragment().show(supportFragmentManager, "gridOptions")
+ }
+
+ /**
+ * Callback from AddDirectoryActivity. Applies any changes necessary to the GameGridActivity.
+ *
+ * @param requestCode An int describing whether the Activity that is returning did so successfully.
+ * @param resultCode An int describing what Activity is giving us this callback.
+ * @param result The information the returning Activity is providing us.
+ */
+ override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
+ super.onActivityResult(requestCode, resultCode, result)
+
+ // If the user picked a file, as opposed to just backing out.
+ if (resultCode == RESULT_OK) {
+ val uri = result!!.data
+ when (requestCode) {
+ MainPresenter.REQUEST_DIRECTORY -> {
+ if (DirectoryInitialization.preferOldFolderPicker(this)) {
+ presenter.onDirectorySelected(FileBrowserHelper.getSelectedPath(result))
+ } else {
+ presenter.onDirectorySelected(result)
+ }
+ }
+
+ MainPresenter.REQUEST_GAME_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.GAME_LIKE_EXTENSIONS
+ ) { EmulationActivity.launch(this, result.data.toString(), false) }
+
+ MainPresenter.REQUEST_WAD_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.WAD_EXTENSION
+ ) { presenter.installWAD(result.data.toString()) }
+
+ MainPresenter.REQUEST_WII_SAVE_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.BIN_EXTENSION
+ ) { presenter.importWiiSave(result.data.toString()) }
+
+ MainPresenter.REQUEST_NAND_BIN_FILE -> FileBrowserHelper.runAfterExtensionCheck(
+ this, uri, FileBrowserHelper.BIN_EXTENSION
+ ) { presenter.importNANDBin(result.data.toString()) }
+ }
+ } else {
+ MainPresenter.skipRescanningLibrary()
+ }
+ }
+
+ override fun onRequestPermissionsResult(
+ requestCode: Int,
+ permissions: Array<String>,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
+ if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ PermissionsHandler.setWritePermissionDenied()
+ }
+
+ DirectoryInitialization.start(this)
+ GameFileCacheManager.startLoad()
+ }
+ }
+
+ /**
+ * Called when the user requests a refresh by swiping down.
+ */
+ override fun onRefresh() {
+ setRefreshing(true)
+ GameFileCacheManager.startRescan()
+ }
+
+ private fun buildRowsAdapter() {
+ val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
+ gameRows.clear()
+
+ if (!DirectoryInitialization.isWaitingForWriteAccess(this)) {
+ GameFileCacheManager.startLoad()
+ }
+
+ for (platform in Platform.values()) {
+ val row =
+ buildGamesRow(platform, GameFileCacheManager.getGameFilesForPlatform(platform))
+
+ // Add row to the adapter only if it is not empty.
+ if (row != null) {
+ rowsAdapter.add(row)
+ }
+ }
+
+ rowsAdapter.add(buildSettingsRow())
+
+ browseFragment!!.adapter = rowsAdapter
+ }
+
+ private fun buildGamesRow(platform: Platform, gameFiles: Collection<GameFile?>): ListRow? {
+ // If there are no games, don't return a Row.
+ if (gameFiles.isEmpty()) {
+ return null
+ }
+
+ // Create an adapter for this row.
+ val row = ArrayObjectAdapter(GameRowPresenter(this))
+ row.addAll(0, gameFiles)
+
+ // Keep a reference to the row in case we need to refresh it.
+ gameRows.add(row)
+
+ // Create a header for this row.
+ val header = HeaderItem(platform.toInt().toLong(), getString(platform.headerName))
+
+ // Create the row, passing it the filled adapter and the header, and give it to the master adapter.
+ return ListRow(header, row)
+ }
+
+ private fun buildSettingsRow(): ListRow {
+ val rowItems = ArrayObjectAdapter(SettingsRowPresenter())
+ rowItems.apply {
+ add(
+ TvSettingsItem(
+ R.id.menu_settings,
+ R.drawable.ic_settings_tv,
+ R.string.grid_menu_settings
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.button_add_directory,
+ R.drawable.ic_add_tv,
+ R.string.add_directory_title
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_grid_options,
+ R.drawable.ic_list_tv,
+ R.string.grid_menu_grid_options
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_refresh,
+ R.drawable.ic_refresh_tv,
+ R.string.grid_menu_refresh
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_open_file,
+ R.drawable.ic_play_tv,
+ R.string.grid_menu_open_file
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_install_wad,
+ R.drawable.ic_folder_tv,
+ R.string.grid_menu_install_wad
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_load_wii_system_menu,
+ R.drawable.ic_folder_tv,
+ R.string.grid_menu_load_wii_system_menu
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_import_wii_save,
+ R.drawable.ic_folder_tv,
+ R.string.grid_menu_import_wii_save
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_import_nand_backup,
+ R.drawable.ic_folder_tv,
+ R.string.grid_menu_import_nand_backup
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_online_system_update,
+ R.drawable.ic_folder_tv,
+ R.string.grid_menu_online_system_update
+ )
+ )
+ add(
+ TvSettingsItem(
+ R.id.menu_about,
+ R.drawable.ic_info_tv,
+ R.string.grid_menu_about
+ )
+ )
+ }
+
+ // Create a header for this row.
+ val header = HeaderItem(R.string.settings.toLong(), getString(R.string.settings))
+ return ListRow(header, rowItems)
+ }
+} \ No newline at end of file
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java
deleted file mode 100644
index f561feb250..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.platform;
-
-import org.dolphinemu.dolphinemu.R;
-
-/**
- * Enum to represent platform (eg GameCube, Wii).
- */
-public enum Platform
-{
- GAMECUBE(0, R.string.platform_gamecube, "GameCube Games"),
- WII(1, R.string.platform_wii, "Wii Games"),
- WIIWARE(2, R.string.platform_wiiware, "WiiWare Games");
-
- private final int value;
- private final int headerName;
- private final String idString;
-
- Platform(int value, int headerName, String idString)
- {
- this.value = value;
- this.headerName = headerName;
- this.idString = idString;
- }
-
- public static Platform fromInt(int i)
- {
- return values()[i];
- }
-
- public static Platform fromNativeInt(int i)
- {
- // TODO: Proper support for DOL and ELF files
- boolean in_range = i >= 0 && i < values().length;
- return values()[in_range ? i : WIIWARE.value];
- }
-
- public static Platform fromPosition(int position)
- {
- return values()[position];
- }
-
- public int toInt()
- {
- return value;
- }
-
- public int getHeaderName()
- {
- return headerName;
- }
-
- public String getIdString()
- {
- return idString;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.kt
new file mode 100644
index 0000000000..0bf3e0ea68
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.kt
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.platform
+
+import org.dolphinemu.dolphinemu.R
+
+/**
+ * Enum to represent platform (eg GameCube, Wii).
+ */
+enum class Platform(private val value: Int, val headerName: Int, val idString: String) {
+ GAMECUBE(0, R.string.platform_gamecube, "GameCube Games"),
+ WII(1, R.string.platform_wii, "Wii Games"),
+ WIIWARE(2, R.string.platform_wiiware, "WiiWare Games");
+
+ fun toInt(): Int {
+ return value
+ }
+
+ companion object {
+ fun fromInt(i: Int): Platform {
+ return values()[i]
+ }
+
+ @JvmStatic
+ fun fromNativeInt(i: Int): Platform {
+ // TODO: Proper support for DOL and ELF files
+ val inRange = i >= 0 && i < values().size
+ return values()[if (inRange) i else WIIWARE.value]
+ }
+
+ @JvmStatic
+ fun fromPosition(position: Int): Platform {
+ return values()[position]
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java
deleted file mode 100644
index 9393a48baf..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java
+++ /dev/null
@@ -1,147 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.platform;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.graphics.Insets;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
-import androidx.fragment.app.Fragment;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
-
-import com.google.android.material.color.MaterialColors;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.adapters.GameAdapter;
-import org.dolphinemu.dolphinemu.databinding.FragmentGridBinding;
-import org.dolphinemu.dolphinemu.layout.AutofitGridLayoutManager;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-
-public final class PlatformGamesFragment extends Fragment implements PlatformGamesView
-{
- private static final String ARG_PLATFORM = "platform";
-
- private SwipeRefreshLayout mSwipeRefresh;
- private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener;
-
- private FragmentGridBinding mBinding;
-
- public static PlatformGamesFragment newInstance(Platform platform)
- {
- PlatformGamesFragment fragment = new PlatformGamesFragment();
-
- Bundle args = new Bundle();
- args.putSerializable(ARG_PLATFORM, platform);
-
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- }
-
- @NonNull
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState)
- {
- mBinding = FragmentGridBinding.inflate(inflater, container, false);
- return mBinding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, Bundle savedInstanceState)
- {
- mSwipeRefresh = mBinding.swipeRefresh;
- GameAdapter adapter = new GameAdapter(requireActivity());
- adapter.setStateRestorationPolicy(
- RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
- mBinding.gridGames.setAdapter(adapter);
- mBinding.gridGames.setLayoutManager(new AutofitGridLayoutManager(requireContext(),
- getResources().getDimensionPixelSize(R.dimen.card_width)));
-
- // Set theme color to the refresh animation's background
- mSwipeRefresh.setProgressBackgroundColorSchemeColor(
- MaterialColors.getColor(mSwipeRefresh, R.attr.colorPrimary));
- mSwipeRefresh.setColorSchemeColors(
- MaterialColors.getColor(mSwipeRefresh, R.attr.colorOnPrimary));
-
- mSwipeRefresh.setOnRefreshListener(mOnRefreshListener);
-
- setInsets();
-
- setRefreshing(GameFileCacheManager.isLoadingOrRescanning());
-
- showGames();
- }
-
- @Override
- public void onDestroyView()
- {
- super.onDestroyView();
- mBinding = null;
- }
-
- @Override
- public void onItemClick(String gameId)
- {
- // No-op for now
- }
-
- @Override
- public void showGames()
- {
- if (mBinding == null)
- return;
-
- if (mBinding.gridGames.getAdapter() != null)
- {
- Platform platform = (Platform) getArguments().getSerializable(ARG_PLATFORM);
- ((GameAdapter) mBinding.gridGames.getAdapter()).swapDataSet(
- GameFileCacheManager.getGameFilesForPlatform(platform));
- }
- }
-
- @Override
- public void refetchMetadata()
- {
- ((GameAdapter) mBinding.gridGames.getAdapter()).refetchMetadata();
- }
-
- public void setOnRefreshListener(@Nullable SwipeRefreshLayout.OnRefreshListener listener)
- {
- mOnRefreshListener = listener;
-
- if (mSwipeRefresh != null)
- {
- mSwipeRefresh.setOnRefreshListener(listener);
- }
- }
-
- public void setRefreshing(boolean refreshing)
- {
- mBinding.swipeRefresh.setRefreshing(refreshing);
- }
-
- private void setInsets()
- {
- ViewCompat.setOnApplyWindowInsetsListener(mBinding.gridGames, (v, windowInsets) ->
- {
- Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
- v.setPadding(0, 0, 0,
- insets.bottom + getResources().getDimensionPixelSize(R.dimen.spacing_list) +
- getResources().getDimensionPixelSize(R.dimen.spacing_fab));
- return windowInsets;
- });
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt
new file mode 100644
index 0000000000..5f1139da8a
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.platform
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.RecyclerView
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
+import com.google.android.material.color.MaterialColors
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.adapters.GameAdapter
+import org.dolphinemu.dolphinemu.databinding.FragmentGridBinding
+import org.dolphinemu.dolphinemu.layout.AutofitGridLayoutManager
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+
+class PlatformGamesFragment : Fragment(), PlatformGamesView {
+ private var swipeRefresh: SwipeRefreshLayout? = null
+ private var onRefreshListener: OnRefreshListener? = null
+
+ private var _binding: FragmentGridBinding? = null
+ private val binding: FragmentGridBinding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentGridBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ swipeRefresh = binding.swipeRefresh
+ val gameAdapter = GameAdapter(requireActivity())
+ gameAdapter.stateRestorationPolicy =
+ RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
+
+ binding.gridGames.apply {
+ adapter = gameAdapter
+ layoutManager = AutofitGridLayoutManager(
+ requireContext(),
+ resources.getDimensionPixelSize(R.dimen.card_width)
+ )
+ }
+
+ // Set theme color to the refresh animation's background
+ binding.swipeRefresh.apply {
+ setProgressBackgroundColorSchemeColor(
+ MaterialColors.getColor(swipeRefresh!!, R.attr.colorPrimary)
+ )
+ setColorSchemeColors(MaterialColors.getColor(swipeRefresh!!, R.attr.colorOnPrimary))
+ setOnRefreshListener(onRefreshListener)
+ }
+
+ setInsets()
+ setRefreshing(GameFileCacheManager.isLoadingOrRescanning())
+ showGames()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun showGames() {
+ if (_binding == null)
+ return
+
+ if (binding.gridGames.adapter != null) {
+ val platform = requireArguments().getSerializable(ARG_PLATFORM) as Platform
+ (binding.gridGames.adapter as GameAdapter?)!!.swapDataSet(
+ GameFileCacheManager.getGameFilesForPlatform(platform)
+ )
+ }
+ }
+
+ override fun refetchMetadata() {
+ (binding.gridGames.adapter as GameAdapter).refetchMetadata()
+ }
+
+ fun setOnRefreshListener(listener: OnRefreshListener?) {
+ onRefreshListener = listener
+ swipeRefresh?.setOnRefreshListener(listener)
+ }
+
+ override fun setRefreshing(refreshing: Boolean) {
+ binding.swipeRefresh.isRefreshing = refreshing
+ }
+
+ private fun setInsets() {
+ ViewCompat.setOnApplyWindowInsetsListener(binding.gridGames) { v: View, windowInsets: WindowInsetsCompat ->
+ val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(
+ 0,
+ 0,
+ 0,
+ insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_list)
+ + resources.getDimensionPixelSize(R.dimen.spacing_fab)
+ )
+ windowInsets
+ }
+ }
+
+ companion object {
+ private const val ARG_PLATFORM = "platform"
+
+ @JvmStatic
+ fun newInstance(platform: Platform?): PlatformGamesFragment {
+ val fragment = PlatformGamesFragment()
+ val args = Bundle()
+ args.putSerializable(ARG_PLATFORM, platform)
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java
deleted file mode 100644
index 642c531946..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.ui.platform;
-
-/**
- * Abstraction for a screen representing a single platform's games.
- */
-public interface PlatformGamesView
-{
- /**
- * Pass a click event to the view's Presenter. Typically called from the
- * view's list adapter.
- *
- * @param gameId The ID of the game that was clicked.
- */
- void onItemClick(String gameId);
-
- /**
- * Shows or hides the loading indicator.
- */
- void setRefreshing(boolean refreshing);
-
- /**
- * To be called when the game file cache is updated.
- */
- void showGames();
-
- /**
- * Re-fetches game metadata from the game file cache.
- */
- void refetchMetadata();
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.kt
new file mode 100644
index 0000000000..b9d188c2e9
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.kt
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.ui.platform
+
+/**
+ * Abstraction for a screen representing a single platform's games.
+ */
+interface PlatformGamesView {
+ /**
+ * Pass a click event to the view's Presenter. Typically called from the
+ * view's list adapter.
+ *
+ * @param gameId The ID of the game that was clicked.
+ */
+ fun onItemClick(gameId: String) { /* Empty default impl */ }
+
+ /**
+ * Shows or hides the loading indicator.
+ */
+ fun setRefreshing(refreshing: Boolean)
+
+ /**
+ * To be called when the game file cache is updated.
+ */
+ fun showGames()
+
+ /**
+ * Re-fetches game metadata from the game file cache.
+ */
+ fun refetchMetadata()
+}