diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java index bb65b855be06..7812e6ead587 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/MainActivity.java @@ -8,10 +8,11 @@ import android.database.Cursor; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.TabLayout; +import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.GridLayoutManager; -import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.Menu; @@ -21,7 +22,9 @@ import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.adapters.GameAdapter; +import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter; +import org.dolphinemu.dolphinemu.fragments.PlatformGamesFragment; +import org.dolphinemu.dolphinemu.model.Game; import org.dolphinemu.dolphinemu.model.GameDatabase; import org.dolphinemu.dolphinemu.model.GameProvider; import org.dolphinemu.dolphinemu.services.AssetCopyService; @@ -34,10 +37,17 @@ public final class MainActivity extends AppCompatActivity implements LoaderManag { private static final int REQUEST_ADD_DIRECTORY = 1; - private static final int LOADER_ID_GAMES = 1; - // TODO When each platform has its own tab, there should be a LOADER_ID for each platform. + /** + * It is important to keep track of loader ID separately from platform ID (see Game.java) + * because we could potentially have Loaders that load things other than Games. + */ + public static final int LOADER_ID_ALL = 100; // TODO + public static final int LOADER_ID_GAMECUBE = 0; + public static final int LOADER_ID_WII = 1; + public static final int LOADER_ID_WIIWARE = 2; - private GameAdapter mAdapter; + private ViewPager mViewPager; + private PlatformPagerAdapter mPlatformPagerAdapter; @Override protected void onCreate(Bundle savedInstanceState) @@ -45,29 +55,26 @@ protected void onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + // Set up the Toolbar. Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_main); setSupportActionBar(toolbar); - FloatingActionButton buttonAddDirectory = (FloatingActionButton) findViewById(R.id.button_add_directory); - RecyclerView recyclerView = (RecyclerView) findViewById(R.id.grid_games); - // TODO Rather than calling into native code, this should use the commented line below. // String versionName = BuildConfig.VERSION_NAME; String versionName = NativeLibrary.GetVersionString(); toolbar.setSubtitle(versionName); - // Specifying the LayoutManager determines how the RecyclerView arranges views. - RecyclerView.LayoutManager layoutManager = new GridLayoutManager(this, - getResources().getInteger(R.integer.game_grid_columns)); - recyclerView.setLayoutManager(layoutManager); + // Set up the Tab bar. + mViewPager = (ViewPager) findViewById(R.id.pager_platforms); - recyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8)); + mPlatformPagerAdapter = new PlatformPagerAdapter(getFragmentManager(), this); + mViewPager.setAdapter(mPlatformPagerAdapter); - // Create an adapter that will relate the dataset to the views on-screen. - getLoaderManager().initLoader(LOADER_ID_GAMES, null, this); - mAdapter = new GameAdapter(); - recyclerView.setAdapter(mAdapter); + TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs_platforms); + tabLayout.setupWithViewPager(mViewPager); + // Set up the FAB. + FloatingActionButton buttonAddDirectory = (FloatingActionButton) findViewById(R.id.button_add_directory); buttonAddDirectory.setOnClickListener(new View.OnClickListener() { @Override @@ -115,7 +122,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent result) // other activities might use this callback in the future (don't forget to change Javadoc!) if (requestCode == REQUEST_ADD_DIRECTORY) { - getLoaderManager().restartLoader(LOADER_ID_GAMES, null, this); + refreshFragment(); } } } @@ -147,7 +154,7 @@ public boolean onOptionsItemSelected(MenuItem item) case R.id.menu_refresh: getContentResolver().insert(GameProvider.URI_REFRESH, null); - getLoaderManager().restartLoader(LOADER_ID_GAMES, null, this); + refreshFragment(); return true; } @@ -155,6 +162,15 @@ public boolean onOptionsItemSelected(MenuItem item) return false; } + public void refreshFragment() + { + PlatformGamesFragment fragment = getPlatformFragment(mViewPager.getCurrentItem()); + if (fragment != null) + { + fragment.refresh(); + } + } + /** * Callback that's invoked when the system has initialized the Loader and @@ -173,16 +189,30 @@ public Loader onCreateLoader(int id, Bundle args) // Take action based on the ID of the Loader that's being created. switch (id) { - case LOADER_ID_GAMES: + case LOADER_ID_ALL: + // TODO Play some sort of load-starting animation; maybe fade the list out. + + return new CursorLoader( + this, // Parent activity context + GameProvider.URI_GAME, // URI of table to query + null, // Return all columns + null, // No selection clause + null, // No selection arguments + GameDatabase.KEY_GAME_TITLE + " asc" // Sort by game name, ascending order + ); + + case LOADER_ID_GAMECUBE: + case LOADER_ID_WII: + case LOADER_ID_WIIWARE: // TODO Play some sort of load-starting animation; maybe fade the list out. return new CursorLoader( - this, // Parent activity context - GameProvider.URI_GAME, // URI of table to query - null, // Return all columns - null, // No selection clause - null, // No selection arguments - GameDatabase.KEY_GAME_TITLE + " asc" // Sort by game name, ascending order + this, // Parent activity context + GameProvider.URI_GAME, // URI of table to query + null, // Return all columns + GameDatabase.KEY_GAME_PLATFORM + " = ?", // Select by platform + new String[]{Integer.toString(id)}, // Platform id is Loader id minus 1 + GameDatabase.KEY_GAME_TITLE + " asc" // Sort by game name, ascending order ); default: @@ -205,25 +235,73 @@ public void onLoadFinished(Loader loader, Cursor data) int id = loader.getId(); Log.d("DolphinEmu", "Loader finished with id: " + id); - // TODO When each platform has its own tab, this should just call into those tabs instead. + PlatformGamesFragment fragment = null; switch (id) { - case LOADER_ID_GAMES: - mAdapter.swapCursor(data); - // TODO Play some sort of load-finished animation; maybe fade the list in. + case LOADER_ID_GAMECUBE: + fragment = getPlatformFragment(Game.PLATFORM_GC); + break; + + case LOADER_ID_WII: + fragment = getPlatformFragment(Game.PLATFORM_WII); break; + case LOADER_ID_WIIWARE: + fragment = getPlatformFragment(Game.PLATFORM_WII_WARE); + break; + + // TODO case LOADER_ID_ALL: + default: Log.e("DolphinEmu", "Bad ID passed in."); + break; } + if (fragment != null) + { + fragment.onLoadFinished(loader, data); + } } @Override public void onLoaderReset(Loader loader) { - Log.d("DolphinEmu", "Loader resetting."); + int id = loader.getId(); + Log.e("DolphinEmu", "Loader resetting with id: " + id); + + PlatformGamesFragment fragment = null; + switch (id) + { + case LOADER_ID_GAMECUBE: + fragment = getPlatformFragment(Game.PLATFORM_GC); + break; + + case LOADER_ID_WII: + fragment = getPlatformFragment(Game.PLATFORM_WII); + break; + + case LOADER_ID_WIIWARE: + fragment = getPlatformFragment(Game.PLATFORM_WII_WARE); + break; + + // TODO case LOADER_ID_ALL: + + default: + Log.e("DolphinEmu", "Bad ID passed in."); + break; + } + + if (fragment != null) + { + fragment.onLoaderReset(); + } + } + + @Nullable + public PlatformGamesFragment getPlatformFragment(int platform) + { + String fragmentTag = "android:switcher:" + mViewPager.getId() + ":" + platform; - // TODO ¯\_(ツ)_/¯ + return (PlatformGamesFragment) getFragmentManager().findFragmentByTag(fragmentTag); } } 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 new file mode 100644 index 000000000000..feeb9ee50f4b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/PlatformPagerAdapter.java @@ -0,0 +1,61 @@ +package org.dolphinemu.dolphinemu.adapters; + + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v13.app.FragmentPagerAdapter; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ImageSpan; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.fragments.PlatformGamesFragment; + +public class PlatformPagerAdapter extends FragmentPagerAdapter +{ + private Context mContext; + + private final static int[] TAB_ICONS = { + R.drawable.ic_gamecube, + R.drawable.ic_wii, + R.drawable.ic_folder// wiiware TODO Have an icon here. + }; + + public PlatformPagerAdapter(FragmentManager fm, Context context) + { + super(fm); + mContext = context; + } + + @Override + public Fragment getItem(int position) + { + return PlatformGamesFragment.newInstance(position); + } + + @Override + public int getCount() + { + return TAB_ICONS.length; + } + + @Override + public CharSequence getPageTitle(int position) + { + // Hax from https://guides.codepath.com/android/Google-Play-Style-Tabs-using-TabLayout#design-support-library + // Apparently a workaround for TabLayout not supporting icons. + // TODO This workaround will eventually not be necessary; switch to more legit methods when that is the case + // TODO Also remove additional hax from styles.xml + Drawable drawable = mContext.getResources().getDrawable(TAB_ICONS[position]); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + + ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); + + SpannableString sb = new SpannableString(" "); + sb.setSpan(imageSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + return sb; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java new file mode 100644 index 000000000000..4957090ed3fb --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/PlatformGamesFragment.java @@ -0,0 +1,110 @@ +package org.dolphinemu.dolphinemu.fragments; + +import android.app.Activity; +import android.app.Fragment; +import android.app.LoaderManager; +import android.content.Loader; +import android.database.Cursor; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.dolphinemu.dolphinemu.BuildConfig; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.adapters.GameAdapter; + +public class PlatformGamesFragment extends Fragment +{ + private static final String ARG_PLATFORM = BuildConfig.APPLICATION_ID + ".PLATFORM"; + + private int mPlatform; + + private GameAdapter mAdapter; + + public static PlatformGamesFragment newInstance(int platform) + { + PlatformGamesFragment fragment = new PlatformGamesFragment(); + + Bundle args = new Bundle(); + args.putInt(ARG_PLATFORM, platform); + + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mPlatform = getArguments().getInt(ARG_PLATFORM); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + View rootView = inflater.inflate(R.layout.fragment_grid, container, false); + RecyclerView recyclerView = (RecyclerView) rootView.findViewById(R.id.grid_games); + + // Specifying the LayoutManager determines how the RecyclerView arranges views. + + RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), + getResources().getInteger(R.integer.game_grid_columns)); + recyclerView.setLayoutManager(layoutManager); + + recyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8)); + + // Create an adapter that will relate the dataset to the views on-screen. +1 because of LOADER_ID_ALL + getLoaderManager().initLoader(mPlatform, null, + (LoaderManager.LoaderCallbacks) getActivity()); + + mAdapter = new GameAdapter(); + recyclerView.setAdapter(mAdapter); + + return rootView; + } + + @Override + public void onAttach(Activity activity) + { + super.onAttach(activity); + } + + public void refresh() + { + Log.d("DolphinEmu", "[PlatformGamesFragment] " + mPlatform + ": Refreshing..."); + // +1 because of LOADER_ID_ALL + getLoaderManager().restartLoader(mPlatform, null, (LoaderManager.LoaderCallbacks) getActivity()); + } + + public void onLoadFinished(Loader loader, Cursor data) + { + // TODO Play some sort of load-finished animation; maybe fade the list in. + + Log.d("DolphinEmu", "[PlatformGamesFragment] " + mPlatform + ": Load finished, swapping cursor..."); + Log.d("DolphinEmu", "[PlatformGamesFragment] " + mPlatform + ": Cursor size: " + data.getCount()); + if (mAdapter != null) + { + mAdapter.swapCursor(data); + } + else + { + Log.e("DolphinEmu", "[PlatformGamesFragment] " + mPlatform + ": No adapter available."); + } + } + + public void onLoaderReset() + { + Log.e("DolphinEmu", "[PlatformGamesFragment] " + mPlatform + ": Loader reset; clearing data from view."); + if (mAdapter != null) + { + mAdapter.swapCursor(null); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java index 28ef08f32e08..6aaee3ae1629 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java @@ -196,7 +196,14 @@ public void scanLibrary(SQLiteDatabase database) gameId = filePath.substring(filePath.lastIndexOf("/") + 1, filePath.lastIndexOf(".")); } - ContentValues game = Game.asContentValues(NativeLibrary.GetPlatform(filePath), + // If the game's platform field is empty, file under Wiiware. // TODO Something less dum + int platform = NativeLibrary.GetPlatform(filePath); + if (platform == -1) + { + platform = Game.PLATFORM_WII_WARE; + } + + ContentValues game = Game.asContentValues(platform, name, NativeLibrary.GetDescription(filePath).replace("\n", " "), NativeLibrary.GetCountry(filePath), diff --git a/Source/Android/app/src/main/res/layout/activity_main.xml b/Source/Android/app/src/main/res/layout/activity_main.xml index 8ebddcc5d585..53d4de6e7233 100644 --- a/Source/Android/app/src/main/res/layout/activity_main.xml +++ b/Source/Android/app/src/main/res/layout/activity_main.xml @@ -1,7 +1,6 @@ + + + - \ 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 new file mode 100644 index 000000000000..eb46ff84585f --- /dev/null +++ b/Source/Android/app/src/main/res/layout/fragment_grid.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/values/styles.xml b/Source/Android/app/src/main/res/values/styles.xml index 374ec4d62823..98cf98480796 100644 --- a/Source/Android/app/src/main/res/values/styles.xml +++ b/Source/Android/app/src/main/res/values/styles.xml @@ -80,4 +80,11 @@ + + + + + \ No newline at end of file