From 4752ec8074d762e4896f7a16e7f6c4aac0227f0a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 20 Feb 2021 19:41:41 +0100 Subject: [PATCH] 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"> + + + +