mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-10 14:39:01 +01:00
Merge pull request #11886 from t895/kotlin-ui
Android: Convert "ui" package to Kotlin
This commit is contained in:
commit
36ca747d55
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
@ -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()
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
@ -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()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user