Merge pull request #9530 from JosJuice/android-loading-indicator

Android: Use SwipeRefreshLayout in MainActivity
This commit is contained in:
Léo Lam 2021-03-04 12:42:20 +01:00 committed by GitHub
commit 794e093223
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 221 additions and 63 deletions

View File

@ -67,8 +67,7 @@ public class AppLinkActivity extends FragmentActivity
mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner(); mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner();
mAfterDirectoryInitializationRunner.run(this, true, () -> tryPlay(playAction)); mAfterDirectoryInitializationRunner.run(this, true, () -> tryPlay(playAction));
IntentFilter gameFileCacheIntentFilter = new IntentFilter( IntentFilter gameFileCacheIntentFilter = new IntentFilter(GameFileCacheService.DONE_LOADING);
GameFileCacheService.BROADCAST_ACTION);
BroadcastReceiver gameFileCacheReceiver = new BroadcastReceiver() BroadcastReceiver gameFileCacheReceiver = new BroadcastReceiver()
{ {
@ -109,7 +108,7 @@ public class AppLinkActivity extends FragmentActivity
// If game == null and the load isn't done, wait for the next GameFileCacheService broadcast. // If game == null and the load isn't done, wait for the next GameFileCacheService broadcast.
// If game == null and the load is done, call play with a null game, making us exit in failure. // If game == null and the load is done, call play with a null game, making us exit in failure.
if (game != null || GameFileCacheService.hasLoadedCache()) if (game != null || !GameFileCacheService.isLoading())
{ {
play(action, game); play(action, game);
} }

View File

@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.ui.platform.Platform;
@ -18,6 +19,7 @@ import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesFragment;
public class PlatformPagerAdapter extends FragmentPagerAdapter public class PlatformPagerAdapter extends FragmentPagerAdapter
{ {
private Context mContext; private Context mContext;
private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener;
private final static int[] TAB_ICONS = private final static int[] TAB_ICONS =
{ {
@ -26,17 +28,19 @@ public class PlatformPagerAdapter extends FragmentPagerAdapter
R.drawable.ic_folder R.drawable.ic_folder
}; };
public PlatformPagerAdapter(FragmentManager fm, Context context) public PlatformPagerAdapter(FragmentManager fm, Context context,
SwipeRefreshLayout.OnRefreshListener onRefreshListener)
{ {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
mContext = context; mContext = context;
mOnRefreshListener = onRefreshListener;
} }
@NonNull @NonNull
@Override @Override
public Fragment getItem(int position) public Fragment getItem(int position)
{ {
return PlatformGamesFragment.newInstance(Platform.fromPosition(position)); return PlatformGamesFragment.newInstance(Platform.fromPosition(position), mOnRefreshListener);
} }
@Override @Override

View File

@ -116,6 +116,8 @@ public class GameFileCache
return cacheChanged; return cacheChanged;
} }
public native int getSize();
public native GameFile[] getAllGames(); public native GameFile[] getAllGames();
public native GameFile addOrGet(String gamePath); public native GameFile addOrGet(String gamePath);

View File

@ -15,7 +15,7 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/** /**
@ -23,7 +23,17 @@ import java.util.concurrent.atomic.AtomicReference;
*/ */
public final class GameFileCacheService extends IntentService public final class GameFileCacheService extends IntentService
{ {
public static final String BROADCAST_ACTION = "org.dolphinemu.dolphinemu.GAME_FILE_CACHE_UPDATED"; /**
* This is broadcast when the contents of the cache change.
*/
public static final String CACHE_UPDATED = "org.dolphinemu.dolphinemu.GAME_FILE_CACHE_UPDATED";
/**
* This is broadcast when the service is done with all requested work, regardless of whether
* the contents of the cache actually changed. (Maybe the cache was already up to date.)
*/
public static final String DONE_LOADING =
"org.dolphinemu.dolphinemu.GAME_FILE_CACHE_DONE_LOADING";
private static final String ACTION_LOAD = "org.dolphinemu.dolphinemu.LOAD_GAME_FILE_CACHE"; private static final String ACTION_LOAD = "org.dolphinemu.dolphinemu.LOAD_GAME_FILE_CACHE";
private static final String ACTION_RESCAN = "org.dolphinemu.dolphinemu.RESCAN_GAME_FILE_CACHE"; private static final String ACTION_RESCAN = "org.dolphinemu.dolphinemu.RESCAN_GAME_FILE_CACHE";
@ -31,8 +41,7 @@ public final class GameFileCacheService extends IntentService
private static GameFileCache gameFileCache = null; private static GameFileCache gameFileCache = null;
private static final AtomicReference<GameFile[]> gameFiles = private static final AtomicReference<GameFile[]> gameFiles =
new AtomicReference<>(new GameFile[]{}); new AtomicReference<>(new GameFile[]{});
private static final AtomicBoolean hasLoadedCache = new AtomicBoolean(false); private static final AtomicInteger unhandledIntents = new AtomicInteger(0);
private static final AtomicBoolean hasScannedLibrary = new AtomicBoolean(false);
public GameFileCacheService() public GameFileCacheService()
{ {
@ -96,14 +105,9 @@ public final class GameFileCacheService extends IntentService
return new String[]{gameFile.getPath(), secondFile.getPath()}; return new String[]{gameFile.getPath(), secondFile.getPath()};
} }
public static boolean hasLoadedCache() public static boolean isLoading()
{ {
return hasLoadedCache.get(); return unhandledIntents.get() != 0;
}
public static boolean hasScannedLibrary()
{
return hasScannedLibrary.get();
} }
private static void startService(Context context, String action) private static void startService(Context context, String action)
@ -119,6 +123,8 @@ public final class GameFileCacheService extends IntentService
*/ */
public static void startLoad(Context context) public static void startLoad(Context context)
{ {
unhandledIntents.getAndIncrement();
new AfterDirectoryInitializationRunner().run(context, false, new AfterDirectoryInitializationRunner().run(context, false,
() -> startService(context, ACTION_LOAD)); () -> startService(context, ACTION_LOAD));
} }
@ -130,6 +136,8 @@ public final class GameFileCacheService extends IntentService
*/ */
public static void startRescan(Context context) public static void startRescan(Context context)
{ {
unhandledIntents.getAndIncrement();
new AfterDirectoryInitializationRunner().run(context, false, new AfterDirectoryInitializationRunner().run(context, false,
() -> startService(context, ACTION_RESCAN)); () -> startService(context, ACTION_RESCAN));
} }
@ -156,9 +164,11 @@ public final class GameFileCacheService extends IntentService
{ {
gameFileCache = temp; gameFileCache = temp;
gameFileCache.load(); gameFileCache.load();
if (gameFileCache.getSize() != 0)
{
updateGameFileArray(); updateGameFileArray();
hasLoadedCache.set(true); sendBroadcast(CACHE_UPDATED);
sendBroadcast(); }
} }
} }
@ -169,13 +179,20 @@ public final class GameFileCacheService extends IntentService
{ {
boolean changed = gameFileCache.scanLibrary(); boolean changed = gameFileCache.scanLibrary();
if (changed) if (changed)
{
updateGameFileArray(); updateGameFileArray();
hasScannedLibrary.set(true); sendBroadcast(CACHE_UPDATED);
sendBroadcast();
} }
} }
} }
int intentsLeft = unhandledIntents.decrementAndGet();
if (intentsLeft == 0)
{
sendBroadcast(DONE_LOADING);
}
}
private void updateGameFileArray() private void updateGameFileArray()
{ {
GameFile[] gameFilesTemp = gameFileCache.getAllGames(); GameFile[] gameFilesTemp = gameFileCache.getAllGames();
@ -183,8 +200,8 @@ public final class GameFileCacheService extends IntentService
gameFiles.set(gameFilesTemp); gameFiles.set(gameFilesTemp);
} }
private void sendBroadcast() private void sendBroadcast(String action)
{ {
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_ACTION)); LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(action));
} }
} }

View File

@ -13,6 +13,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
@ -28,6 +29,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView; import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
import org.dolphinemu.dolphinemu.utils.Action1;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner; import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
@ -38,7 +40,8 @@ import org.dolphinemu.dolphinemu.utils.StartupHandler;
* The main Activity of the Lollipop style UI. Manages several PlatformGamesFragments, which * 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. * individually display a grid of available games for each Fragment, in a tabbed layout.
*/ */
public final class MainActivity extends AppCompatActivity implements MainView public final class MainActivity extends AppCompatActivity
implements MainView, SwipeRefreshLayout.OnRefreshListener
{ {
private ViewPager mViewPager; private ViewPager mViewPager;
private Toolbar mToolbar; private Toolbar mToolbar;
@ -79,6 +82,8 @@ public final class MainActivity extends AppCompatActivity implements MainView
{ {
super.onResume(); super.onResume();
boolean cacheAlreadyLoading = GameFileCacheService.isLoading();
if (DirectoryInitialization.shouldStart(this)) if (DirectoryInitialization.shouldStart(this))
{ {
DirectoryInitialization.start(this); DirectoryInitialization.start(this);
@ -90,17 +95,19 @@ public final class MainActivity extends AppCompatActivity implements MainView
// In case the user changed a setting that affects how games are displayed, // In case the user changed a setting that affects how games are displayed,
// such as system language, cover downloading... // such as system language, cover downloading...
refetchMetadata(); forEachPlatformGamesView(PlatformGamesView::refetchMetadata);
if (sShouldRescanLibrary) if (sShouldRescanLibrary && !cacheAlreadyLoading)
{ {
new AfterDirectoryInitializationRunner().run(this, false, () ->
{
setRefreshing(true);
GameFileCacheService.startRescan(this); GameFileCacheService.startRescan(this);
});
} }
else
{
sShouldRescanLibrary = true; sShouldRescanLibrary = true;
} }
}
@Override @Override
protected void onDestroy() protected void onDestroy()
@ -266,26 +273,42 @@ public final class MainActivity extends AppCompatActivity implements MainView
return mPresenter.handleOptionSelection(item.getItemId(), this); return mPresenter.handleOptionSelection(item.getItemId(), this);
} }
public void showGames() /**
* Called when the user requests a refresh by swiping down.
*/
@Override
public void onRefresh()
{ {
for (Platform platform : Platform.values()) setRefreshing(true);
{ GameFileCacheService.startRescan(this);
PlatformGamesView fragment = getPlatformGamesView(platform);
if (fragment != null)
{
fragment.showGames();
}
}
} }
private void refetchMetadata() /**
* 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);
}
private void forEachPlatformGamesView(Action1<PlatformGamesView> action)
{ {
for (Platform platform : Platform.values()) for (Platform platform : Platform.values())
{ {
PlatformGamesView fragment = getPlatformGamesView(platform); PlatformGamesView fragment = getPlatformGamesView(platform);
if (fragment != null) if (fragment != null)
{ {
fragment.refetchMetadata(); action.call(fragment);
} }
} }
} }
@ -302,7 +325,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
private void setPlatformTabsAndStartGameFileCacheService() private void setPlatformTabsAndStartGameFileCacheService()
{ {
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter( PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
getSupportFragmentManager(), this); getSupportFragmentManager(), this, this);
mViewPager.setAdapter(platformPagerAdapter); mViewPager.setAdapter(platformPagerAdapter);
mViewPager.setOffscreenPageLimit(platformPagerAdapter.getCount()); mViewPager.setOffscreenPageLimit(platformPagerAdapter.getCount());
mTabLayout.setupWithViewPager(mViewPager); mTabLayout.setupWithViewPager(mViewPager);

View File

@ -53,13 +53,22 @@ public final class MainPresenter
mView.setVersionString(versionName); mView.setVersionString(versionName);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
filter.addAction(GameFileCacheService.BROADCAST_ACTION); filter.addAction(GameFileCacheService.CACHE_UPDATED);
filter.addAction(GameFileCacheService.DONE_LOADING);
mBroadcastReceiver = new BroadcastReceiver() mBroadcastReceiver = new BroadcastReceiver()
{ {
@Override @Override
public void onReceive(Context context, Intent intent) public void onReceive(Context context, Intent intent)
{ {
switch (intent.getAction())
{
case GameFileCacheService.CACHE_UPDATED:
mView.showGames(); mView.showGames();
break;
case GameFileCacheService.DONE_LOADING:
mView.setRefreshing(false);
break;
}
} }
}; };
LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, filter); LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, filter);
@ -87,6 +96,7 @@ public final class MainPresenter
return true; return true;
case R.id.menu_refresh: case R.id.menu_refresh:
mView.setRefreshing(true);
GameFileCacheService.startRescan(context); GameFileCacheService.startRescan(context);
return true; return true;

View File

@ -23,6 +23,11 @@ public interface MainView
void launchOpenFileActivity(int requestCode); void launchOpenFileActivity(int requestCode);
/**
* Shows or hides the loading indicator.
*/
void setRefreshing(boolean refreshing);
/** /**
* To be called when the game file cache is updated. * To be called when the game file cache is updated.
*/ */

View File

@ -4,6 +4,7 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.TypedValue;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -15,6 +16,7 @@ import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem; import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow; import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.ListRowPresenter; import androidx.leanback.widget.ListRowPresenter;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.activities.EmulationActivity;
@ -26,6 +28,7 @@ import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.model.TvSettingsItem; import org.dolphinemu.dolphinemu.model.TvSettingsItem;
import org.dolphinemu.dolphinemu.services.GameFileCacheService; import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper; import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler; import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
@ -36,12 +39,15 @@ import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; 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 static boolean sShouldRescanLibrary = true;
private final MainPresenter mPresenter = new MainPresenter(this, this); private final MainPresenter mPresenter = new MainPresenter(this, this);
private SwipeRefreshLayout mSwipeRefresh;
private BrowseSupportFragment mBrowseFragment; private BrowseSupportFragment mBrowseFragment;
private final ArrayList<ArrayObjectAdapter> mGameRows = new ArrayList<>(); private final ArrayList<ArrayObjectAdapter> mGameRows = new ArrayList<>();
@ -68,6 +74,8 @@ public final class TvMainActivity extends FragmentActivity implements MainView
{ {
super.onResume(); super.onResume();
boolean cacheAlreadyLoading = GameFileCacheService.isLoading();
if (DirectoryInitialization.shouldStart(this)) if (DirectoryInitialization.shouldStart(this))
{ {
DirectoryInitialization.start(this); DirectoryInitialization.start(this);
@ -80,15 +88,17 @@ public final class TvMainActivity extends FragmentActivity implements MainView
// such as system language, cover downloading... // such as system language, cover downloading...
refetchMetadata(); refetchMetadata();
if (sShouldRescanLibrary) if (sShouldRescanLibrary && !cacheAlreadyLoading)
{ {
new AfterDirectoryInitializationRunner().run(this, false, () ->
{
setRefreshing(true);
GameFileCacheService.startRescan(this); GameFileCacheService.startRescan(this);
});
} }
else
{
sShouldRescanLibrary = true; sShouldRescanLibrary = true;
} }
}
@Override @Override
protected void onDestroy() protected void onDestroy()
@ -117,6 +127,16 @@ public final class TvMainActivity extends FragmentActivity implements MainView
void setupUI() 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(); final FragmentManager fragmentManager = getSupportFragmentManager();
mBrowseFragment = new BrowseSupportFragment(); mBrowseFragment = new BrowseSupportFragment();
fragmentManager fragmentManager
@ -188,6 +208,15 @@ public final class TvMainActivity extends FragmentActivity implements MainView
startActivityForResult(intent, requestCode); startActivityForResult(intent, requestCode);
} }
/**
* Shows or hides the loading indicator.
*/
@Override
public void setRefreshing(boolean refreshing)
{
mSwipeRefresh.setRefreshing(refreshing);
}
@Override @Override
public void showGames() public void showGames()
{ {
@ -277,6 +306,16 @@ public final class TvMainActivity extends FragmentActivity implements MainView
} }
} }
/**
* Called when the user requests a refresh by swiping down.
*/
@Override
public void onRefresh()
{
setRefreshing(true);
GameFileCacheService.startRescan(this);
}
private void buildRowsAdapter() private void buildRowsAdapter()
{ {
ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

View File

@ -1,14 +1,17 @@
package org.dolphinemu.dolphinemu.ui.platform; package org.dolphinemu.dolphinemu.ui.platform;
import android.os.Bundle; import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.adapters.GameAdapter; import org.dolphinemu.dolphinemu.adapters.GameAdapter;
@ -20,10 +23,13 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
private GameAdapter mAdapter; private GameAdapter mAdapter;
private RecyclerView mRecyclerView; 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(); Bundle args = new Bundle();
args.putSerializable(ARG_PLATFORM, platform); args.putSerializable(ARG_PLATFORM, platform);
@ -32,6 +38,11 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
return fragment; return fragment;
} }
public PlatformGamesFragment(SwipeRefreshLayout.OnRefreshListener onRefreshListener)
{
mOnRefreshListener = onRefreshListener;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState)
{ {
@ -55,11 +66,19 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns); RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
mAdapter = new GameAdapter(); 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.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter); mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8)); mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8));
setRefreshing(GameFileCacheService.isLoading());
showGames(); showGames();
} }
@ -91,8 +110,14 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
mAdapter.refetchMetadata(); mAdapter.refetchMetadata();
} }
public void setRefreshing(boolean refreshing)
{
mSwipeRefresh.setRefreshing(refreshing);
}
private void findViews(View root) private void findViews(View root)
{ {
mSwipeRefresh = root.findViewById(R.id.swipe_refresh);
mRecyclerView = root.findViewById(R.id.grid_games); mRecyclerView = root.findViewById(R.id.grid_games);
} }
} }

View File

@ -1,5 +1,8 @@
package org.dolphinemu.dolphinemu.ui.platform; 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. * Abstraction for a screen representing a single platform's games.
*/ */
@ -21,6 +24,11 @@ public interface PlatformGamesView
*/ */
void onItemClick(String gameId); void onItemClick(String gameId);
/**
* Shows or hides the loading indicator.
*/
void setRefreshing(boolean refreshing);
/** /**
* To be called when the game file cache is updated. * To be called when the game file cache is updated.
*/ */

View File

@ -1,7 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<!-- 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:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
</FrameLayout> </FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -4,12 +4,19 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<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">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_games" android:id="@+id/grid_games"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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"/> tools:listitem="@layout/card_game"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout> </FrameLayout>

View File

@ -39,6 +39,12 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_finali
delete GetPointer(env, obj); 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 JNIEXPORT jobjectArray JNICALL
Java_org_dolphinemu_dolphinemu_model_GameFileCache_getAllGames(JNIEnv* env, jobject obj) Java_org_dolphinemu_dolphinemu_model_GameFileCache_getAllGames(JNIEnv* env, jobject obj)
{ {