From 3a8793f93ffe836a7a503905199083867036ad52 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 Feb 2021 17:55:31 +0100 Subject: [PATCH 1/4] Android: Refactor MainActivity, add forEachPlatformGamesView --- .../dolphinemu/ui/main/MainActivity.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index 2beb2112f2..1addf82258 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -28,6 +28,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity; import org.dolphinemu.dolphinemu.services.GameFileCacheService; 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; @@ -90,7 +91,7 @@ public final class MainActivity extends AppCompatActivity implements MainView // In case the user changed a setting that affects how games are displayed, // such as system language, cover downloading... - refetchMetadata(); + forEachPlatformGamesView(PlatformGamesView::refetchMetadata); if (sShouldRescanLibrary) { @@ -266,26 +267,20 @@ public final class MainActivity extends AppCompatActivity implements MainView return mPresenter.handleOptionSelection(item.getItemId(), this); } + @Override public void showGames() { - for (Platform platform : Platform.values()) - { - PlatformGamesView fragment = getPlatformGamesView(platform); - if (fragment != null) - { - fragment.showGames(); - } - } + forEachPlatformGamesView(PlatformGamesView::showGames); } - private void refetchMetadata() + private void forEachPlatformGamesView(Action1 action) { for (Platform platform : Platform.values()) { PlatformGamesView fragment = getPlatformGamesView(platform); if (fragment != null) { - fragment.refetchMetadata(); + action.call(fragment); } } } From dbcdead04d7ceeedc9f26a7ede2aeb69ce9be7df Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 Feb 2021 19:41:41 +0100 Subject: [PATCH 2/4] Android: Split GameFileCacheService broadcasts into two types --- .../activities/AppLinkActivity.java | 5 +- .../dolphinemu/model/GameFileCache.java | 2 + .../services/GameFileCacheService.java | 53 ++++++++++++------- .../dolphinemu/ui/main/MainPresenter.java | 2 +- Source/Android/jni/GameList/GameFileCache.cpp | 6 +++ 5 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java index 156995e26d..4e19b05334 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AppLinkActivity.java @@ -67,8 +67,7 @@ public class AppLinkActivity extends FragmentActivity mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner(); mAfterDirectoryInitializationRunner.run(this, true, () -> tryPlay(playAction)); - IntentFilter gameFileCacheIntentFilter = new IntentFilter( - GameFileCacheService.BROADCAST_ACTION); + IntentFilter gameFileCacheIntentFilter = new IntentFilter(GameFileCacheService.DONE_LOADING); 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 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); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java index a15e734d0b..e359d701bc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java @@ -116,6 +116,8 @@ public class GameFileCache return cacheChanged; } + public native int getSize(); + public native GameFile[] getAllGames(); public native GameFile addOrGet(String gamePath); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java index ab295c5550..34bf1afa0a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java @@ -15,7 +15,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** @@ -23,7 +23,17 @@ import java.util.concurrent.atomic.AtomicReference; */ 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_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 final AtomicReference gameFiles = new AtomicReference<>(new GameFile[]{}); - private static final AtomicBoolean hasLoadedCache = new AtomicBoolean(false); - private static final AtomicBoolean hasScannedLibrary = new AtomicBoolean(false); + private static final AtomicInteger unhandledIntents = new AtomicInteger(0); public GameFileCacheService() { @@ -96,14 +105,9 @@ public final class GameFileCacheService extends IntentService return new String[]{gameFile.getPath(), secondFile.getPath()}; } - public static boolean hasLoadedCache() + public static boolean isLoading() { - return hasLoadedCache.get(); - } - - public static boolean hasScannedLibrary() - { - return hasScannedLibrary.get(); + return unhandledIntents.get() != 0; } private static void startService(Context context, String action) @@ -119,6 +123,8 @@ public final class GameFileCacheService extends IntentService */ public static void startLoad(Context context) { + unhandledIntents.getAndIncrement(); + new AfterDirectoryInitializationRunner().run(context, false, () -> startService(context, ACTION_LOAD)); } @@ -130,6 +136,8 @@ public final class GameFileCacheService extends IntentService */ public static void startRescan(Context context) { + unhandledIntents.getAndIncrement(); + new AfterDirectoryInitializationRunner().run(context, false, () -> startService(context, ACTION_RESCAN)); } @@ -156,9 +164,11 @@ public final class GameFileCacheService extends IntentService { gameFileCache = temp; gameFileCache.load(); - updateGameFileArray(); - hasLoadedCache.set(true); - sendBroadcast(); + if (gameFileCache.getSize() != 0) + { + updateGameFileArray(); + sendBroadcast(CACHE_UPDATED); + } } } @@ -169,11 +179,18 @@ public final class GameFileCacheService extends IntentService { boolean changed = gameFileCache.scanLibrary(); if (changed) + { updateGameFileArray(); - hasScannedLibrary.set(true); - sendBroadcast(); + sendBroadcast(CACHE_UPDATED); + } } } + + int intentsLeft = unhandledIntents.decrementAndGet(); + if (intentsLeft == 0) + { + sendBroadcast(DONE_LOADING); + } } private void updateGameFileArray() @@ -183,8 +200,8 @@ public final class GameFileCacheService extends IntentService 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)); } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java index ebd43513ab..ff594b3245 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java @@ -53,7 +53,7 @@ public final class MainPresenter mView.setVersionString(versionName); IntentFilter filter = new IntentFilter(); - filter.addAction(GameFileCacheService.BROADCAST_ACTION); + filter.addAction(GameFileCacheService.CACHE_UPDATED); mBroadcastReceiver = new BroadcastReceiver() { @Override diff --git a/Source/Android/jni/GameList/GameFileCache.cpp b/Source/Android/jni/GameList/GameFileCache.cpp index d5fce915b8..9a3bb49556 100644 --- a/Source/Android/jni/GameList/GameFileCache.cpp +++ b/Source/Android/jni/GameList/GameFileCache.cpp @@ -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(GetPointer(env, obj)->GetSize()); +} + JNIEXPORT jobjectArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_getAllGames(JNIEnv* env, jobject obj) { From cad4548b27e3a29c3d8945c967471b1d7b60fb70 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 Feb 2021 20:54:47 +0100 Subject: [PATCH 3/4] Android: Don't queue up multiple rescans before directory initialization --- .../dolphinemu/dolphinemu/ui/main/MainActivity.java | 10 +++++----- .../dolphinemu/dolphinemu/ui/main/TvMainActivity.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index 1addf82258..f17265db91 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -80,6 +80,8 @@ public final class MainActivity extends AppCompatActivity implements MainView { super.onResume(); + boolean cacheAlreadyLoading = GameFileCacheService.isLoading(); + if (DirectoryInitialization.shouldStart(this)) { DirectoryInitialization.start(this); @@ -93,14 +95,12 @@ public final class MainActivity extends AppCompatActivity implements MainView // such as system language, cover downloading... forEachPlatformGamesView(PlatformGamesView::refetchMetadata); - if (sShouldRescanLibrary) + if (sShouldRescanLibrary && !cacheAlreadyLoading) { GameFileCacheService.startRescan(this); } - else - { - sShouldRescanLibrary = true; - } + + sShouldRescanLibrary = true; } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index d34c3d8518..a568f47156 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -68,6 +68,8 @@ public final class TvMainActivity extends FragmentActivity implements MainView { super.onResume(); + boolean cacheAlreadyLoading = GameFileCacheService.isLoading(); + if (DirectoryInitialization.shouldStart(this)) { DirectoryInitialization.start(this); @@ -80,14 +82,12 @@ public final class TvMainActivity extends FragmentActivity implements MainView // such as system language, cover downloading... refetchMetadata(); - if (sShouldRescanLibrary) + if (sShouldRescanLibrary && !cacheAlreadyLoading) { GameFileCacheService.startRescan(this); } - else - { - sShouldRescanLibrary = true; - } + + sShouldRescanLibrary = true; } @Override From 4752ec8074d762e4896f7a16e7f6c4aac0227f0a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 Feb 2021 19:41:41 +0100 Subject: [PATCH 4/4] Android: Use SwipeRefreshLayout in MainActivity The main reason why I'm adding this isn't actually to allow users to swipe down to refresh, it's to add a loading indicator. Considering that the Storage Access Framework can be slow for folders with many items (many subfolders?), not showing a loading indicator might give users the impression that adding a folder resulted in nothing happening even though Dolphin is scanning for games in the background. But I suppose letting users swipe down to refresh is a nice bonus with the change. --- .../adapters/PlatformPagerAdapter.java | 8 +++- .../dolphinemu/ui/main/MainActivity.java | 34 +++++++++++++-- .../dolphinemu/ui/main/MainPresenter.java | 12 +++++- .../dolphinemu/ui/main/MainView.java | 5 +++ .../dolphinemu/ui/main/TvMainActivity.java | 43 ++++++++++++++++++- .../ui/platform/PlatformGamesFragment.java | 29 ++++++++++++- .../ui/platform/PlatformGamesView.java | 8 ++++ .../src/main/res/layout/activity_tv_main.xml | 23 +++++++--- .../app/src/main/res/layout/fragment_grid.xml | 15 +++++-- 9 files changed, 158 insertions(+), 19 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/PlatformPagerAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/PlatformPagerAdapter.java index f18c7e5438..eef3c5b77d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/PlatformPagerAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/PlatformPagerAdapter.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.ui.platform.Platform; @@ -18,6 +19,7 @@ import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesFragment; public class PlatformPagerAdapter extends FragmentPagerAdapter { private Context mContext; + private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener; private final static int[] TAB_ICONS = { @@ -26,17 +28,19 @@ public class PlatformPagerAdapter extends FragmentPagerAdapter 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); mContext = context; + mOnRefreshListener = onRefreshListener; } @NonNull @Override public Fragment getItem(int position) { - return PlatformGamesFragment.newInstance(Platform.fromPosition(position)); + return PlatformGamesFragment.newInstance(Platform.fromPosition(position), mOnRefreshListener); } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java index f17265db91..8849494d26 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java @@ -13,6 +13,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.viewpager.widget.ViewPager; import com.google.android.material.floatingactionbutton.FloatingActionButton; @@ -39,7 +40,8 @@ import org.dolphinemu.dolphinemu.utils.StartupHandler; * 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 +public final class MainActivity extends AppCompatActivity + implements MainView, SwipeRefreshLayout.OnRefreshListener { private ViewPager mViewPager; private Toolbar mToolbar; @@ -97,7 +99,11 @@ public final class MainActivity extends AppCompatActivity implements MainView if (sShouldRescanLibrary && !cacheAlreadyLoading) { - GameFileCacheService.startRescan(this); + new AfterDirectoryInitializationRunner().run(this, false, () -> + { + setRefreshing(true); + GameFileCacheService.startRescan(this); + }); } sShouldRescanLibrary = true; @@ -267,6 +273,28 @@ public final class MainActivity extends AppCompatActivity implements MainView return mPresenter.handleOptionSelection(item.getItemId(), this); } + /** + * Called when the user requests a refresh by swiping down. + */ + @Override + public void onRefresh() + { + setRefreshing(true); + GameFileCacheService.startRescan(this); + } + + /** + * 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() { @@ -297,7 +325,7 @@ public final class MainActivity extends AppCompatActivity implements MainView private void setPlatformTabsAndStartGameFileCacheService() { PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter( - getSupportFragmentManager(), this); + getSupportFragmentManager(), this, this); mViewPager.setAdapter(platformPagerAdapter); mViewPager.setOffscreenPageLimit(platformPagerAdapter.getCount()); mTabLayout.setupWithViewPager(mViewPager); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java index ff594b3245..803994a67f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java @@ -54,12 +54,21 @@ public final class MainPresenter IntentFilter filter = new IntentFilter(); filter.addAction(GameFileCacheService.CACHE_UPDATED); + filter.addAction(GameFileCacheService.DONE_LOADING); mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - mView.showGames(); + switch (intent.getAction()) + { + case GameFileCacheService.CACHE_UPDATED: + mView.showGames(); + break; + case GameFileCacheService.DONE_LOADING: + mView.setRefreshing(false); + break; + } } }; LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, filter); @@ -87,6 +96,7 @@ public final class MainPresenter return true; case R.id.menu_refresh: + mView.setRefreshing(true); GameFileCacheService.startRescan(context); return true; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java index b2e6b74495..8428104240 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java @@ -23,6 +23,11 @@ public interface MainView void launchOpenFileActivity(int requestCode); + /** + * Shows or hides the loading indicator. + */ + void setRefreshing(boolean refreshing); + /** * To be called when the game file cache is updated. */ diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java index a568f47156..4df042df82 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java @@ -4,6 +4,7 @@ import android.content.Intent; 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.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.R; 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.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 org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder; 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 mGameRows = new ArrayList<>(); @@ -84,7 +90,11 @@ public final class TvMainActivity extends FragmentActivity implements MainView if (sShouldRescanLibrary && !cacheAlreadyLoading) { - GameFileCacheService.startRescan(this); + new AfterDirectoryInitializationRunner().run(this, false, () -> + { + setRefreshing(true); + GameFileCacheService.startRescan(this); + }); } sShouldRescanLibrary = true; @@ -117,6 +127,16 @@ public final class TvMainActivity extends FragmentActivity implements MainView 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 final class TvMainActivity extends FragmentActivity implements MainView 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 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() { ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java index 50d2499705..a3f67613c0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java @@ -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 final class PlatformGamesFragment extends Fragment implements PlatformGam return fragment; } + public PlatformGamesFragment(SwipeRefreshLayout.OnRefreshListener onRefreshListener) + { + mOnRefreshListener = onRefreshListener; + } + @Override public void onCreate(Bundle savedInstanceState) { @@ -55,11 +66,19 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam 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 final class PlatformGamesFragment extends Fragment implements PlatformGam 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); } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java index 918f40c6e0..b4ee22f985 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java @@ -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 @@ public interface PlatformGamesView */ void onItemClick(String gameId); + /** + * Shows or hides the loading indicator. + */ + void setRefreshing(boolean refreshing); + /** * To be called when the game file cache is updated. */ diff --git a/Source/Android/app/src/main/res/layout/activity_tv_main.xml b/Source/Android/app/src/main/res/layout/activity_tv_main.xml index b14f5c56d0..91f86a66ea 100644 --- a/Source/Android/app/src/main/res/layout/activity_tv_main.xml +++ b/Source/Android/app/src/main/res/layout/activity_tv_main.xml @@ -1,7 +1,20 @@ - - \ No newline at end of file + + + + + + + + + diff --git a/Source/Android/app/src/main/res/layout/fragment_grid.xml b/Source/Android/app/src/main/res/layout/fragment_grid.xml index 97faa0ac8d..adedbed4c0 100644 --- a/Source/Android/app/src/main/res/layout/fragment_grid.xml +++ b/Source/Android/app/src/main/res/layout/fragment_grid.xml @@ -4,12 +4,19 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:layout_marginRight="@dimen/activity_horizontal_margin"> + + + +