@@ -4,6 +4,7 @@
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.TypedValue;
import android.widget.Toast;

import androidx.annotation.NonNull;
@@ -15,6 +16,7 @@
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.ListRowPresenter;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
@@ -26,6 +28,7 @@
import org.dolphinemu.dolphinemu.model.TvSettingsItem;
import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
@@ -36,12 +39,15 @@
import java.util.ArrayList;
import java.util.Collection;

public final class TvMainActivity extends FragmentActivity implements MainView
public final class TvMainActivity extends FragmentActivity
implements MainView, SwipeRefreshLayout.OnRefreshListener
{
private static boolean sShouldRescanLibrary = true;

private final MainPresenter mPresenter = new MainPresenter(this, this);

private SwipeRefreshLayout mSwipeRefresh;

private BrowseSupportFragment mBrowseFragment;

private final ArrayList<ArrayObjectAdapter> mGameRows = new ArrayList<>();
@@ -68,6 +74,8 @@ protected void onResume()
{
super.onResume();

boolean cacheAlreadyLoading = GameFileCacheService.isLoading();

if (DirectoryInitialization.shouldStart(this))
{
DirectoryInitialization.start(this);
@@ -80,14 +88,16 @@ protected void onResume()
// such as system language, cover downloading...
refetchMetadata();

if (sShouldRescanLibrary)
{
GameFileCacheService.startRescan(this);
}
else
if (sShouldRescanLibrary && !cacheAlreadyLoading)
{
sShouldRescanLibrary = true;
new AfterDirectoryInitializationRunner().run(this, false, () ->
{
setRefreshing(true);
GameFileCacheService.startRescan(this);
});
}

sShouldRescanLibrary = true;
}

@Override
@@ -117,6 +127,16 @@ protected void onStop()

void setupUI()
{
mSwipeRefresh = findViewById(R.id.swipe_refresh);

TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
mSwipeRefresh.setColorSchemeColors(typedValue.data);

mSwipeRefresh.setOnRefreshListener(this);

setRefreshing(GameFileCacheService.isLoading());

final FragmentManager fragmentManager = getSupportFragmentManager();
mBrowseFragment = new BrowseSupportFragment();
fragmentManager
@@ -188,6 +208,15 @@ public void launchOpenFileActivity(int requestCode)
startActivityForResult(intent, requestCode);
}

/**
* Shows or hides the loading indicator.
*/
@Override
public void setRefreshing(boolean refreshing)
{
mSwipeRefresh.setRefreshing(refreshing);
}

@Override
public void showGames()
{
@@ -277,6 +306,16 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
}
}

/**
* Called when the user requests a refresh by swiping down.
*/
@Override
public void onRefresh()
{
setRefreshing(true);
GameFileCacheService.startRescan(this);
}

private void buildRowsAdapter()
{
ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
@@ -1,14 +1,17 @@
package org.dolphinemu.dolphinemu.ui.platform;

import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.adapters.GameAdapter;
@@ -20,10 +23,13 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam

private GameAdapter mAdapter;
private RecyclerView mRecyclerView;
private SwipeRefreshLayout mSwipeRefresh;
private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener;

public static PlatformGamesFragment newInstance(Platform platform)
public static PlatformGamesFragment newInstance(Platform platform,
SwipeRefreshLayout.OnRefreshListener onRefreshListener)
{
PlatformGamesFragment fragment = new PlatformGamesFragment();
PlatformGamesFragment fragment = new PlatformGamesFragment(onRefreshListener);

Bundle args = new Bundle();
args.putSerializable(ARG_PLATFORM, platform);
@@ -32,6 +38,11 @@ public static PlatformGamesFragment newInstance(Platform platform)
return fragment;
}

public PlatformGamesFragment(SwipeRefreshLayout.OnRefreshListener onRefreshListener)
{
mOnRefreshListener = onRefreshListener;
}

@Override
public void onCreate(Bundle savedInstanceState)
{
@@ -55,11 +66,19 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState)
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
mAdapter = new GameAdapter();

TypedValue typedValue = new TypedValue();
requireActivity().getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
mSwipeRefresh.setColorSchemeColors(typedValue.data);

mSwipeRefresh.setOnRefreshListener(mOnRefreshListener);

mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);

mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8));

setRefreshing(GameFileCacheService.isLoading());

showGames();
}

@@ -91,8 +110,14 @@ public void refetchMetadata()
mAdapter.refetchMetadata();
}

public void setRefreshing(boolean refreshing)
{
mSwipeRefresh.setRefreshing(refreshing);
}

private void findViews(View root)
{
mSwipeRefresh = root.findViewById(R.id.swipe_refresh);
mRecyclerView = root.findViewById(R.id.grid_games);
}
}
@@ -1,5 +1,8 @@
package org.dolphinemu.dolphinemu.ui.platform;

import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;

/**
* Abstraction for a screen representing a single platform's games.
*/
@@ -21,6 +24,11 @@
*/
void onItemClick(String gameId);

/**
* Shows or hides the loading indicator.
*/
void setRefreshing(boolean refreshing);

/**
* To be called when the game file cache is updated.
*/
@@ -1,7 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">

</FrameLayout>
<!-- The SwipeRefreshLayout is used mainly for its ability to show a loading indicator, not for
its ability to detect swipes. But if someone is using this activity with a touchscreen
for whatever reason, we get the ability to refresh by swiping down more or less for free. -->

<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">

<FrameLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">

</FrameLayout>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@@ -4,12 +4,19 @@
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_games"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
tools:listitem="@layout/card_game"/>
android:layout_marginRight="@dimen/activity_horizontal_margin">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_games"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/card_game"/>

</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</FrameLayout>
@@ -39,6 +39,12 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_finali
delete GetPointer(env, obj);
}

JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_getSize(JNIEnv* env,
jobject obj)
{
return static_cast<jint>(GetPointer(env, obj)->GetSize());
}

JNIEXPORT jobjectArray JNICALL
Java_org_dolphinemu_dolphinemu_model_GameFileCache_getAllGames(JNIEnv* env, jobject obj)
{