Android: Add game directory list editor
This commit is contained in:
@ -677,6 +677,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||
}
|
||||
|
||||
private boolean mSustainedPerformanceModeEnabled = false;
|
||||
|
||||
private void updateSustainedPerformanceMode() {
|
||||
final boolean enabled = getBooleanSetting("Main/SustainedPerformanceMode", false);
|
||||
if (mSustainedPerformanceModeEnabled == enabled)
|
||||
|
||||
@ -0,0 +1,279 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.util.Property;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class GameDirectoriesActivity extends AppCompatActivity {
|
||||
private static final int REQUEST_ADD_DIRECTORY_TO_GAME_LIST = 1;
|
||||
|
||||
private class DirectoryListAdapter extends RecyclerView.Adapter {
|
||||
private class Entry {
|
||||
private String mPath;
|
||||
private boolean mRecursive;
|
||||
|
||||
public Entry(String path, boolean recursive) {
|
||||
mPath = path;
|
||||
mRecursive = recursive;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return mPath;
|
||||
}
|
||||
|
||||
public boolean isRecursive() {
|
||||
return mRecursive;
|
||||
}
|
||||
|
||||
public void toggleRecursive() {
|
||||
mRecursive = !mRecursive;
|
||||
}
|
||||
}
|
||||
|
||||
private class EntryComparator implements Comparator<Entry> {
|
||||
@Override
|
||||
public int compare(Entry left, Entry right) {
|
||||
return left.getPath().compareTo(right.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private Entry[] mEntries;
|
||||
|
||||
public DirectoryListAdapter(Context context) {
|
||||
mContext = context;
|
||||
reload();
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Set<String> paths = prefs.getStringSet("GameList/Paths", null);
|
||||
if (paths != null) {
|
||||
for (String path : paths)
|
||||
entries.add(new Entry(path, false));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
try {
|
||||
Set<String> paths = prefs.getStringSet("GameList/RecursivePaths", null);
|
||||
if (paths != null) {
|
||||
for (String path : paths)
|
||||
entries.add(new Entry(path, true));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
mEntries = new Entry[entries.size()];
|
||||
entries.toArray(mEntries);
|
||||
Arrays.sort(mEntries, new EntryComparator());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
private int mPosition;
|
||||
private Entry mEntry;
|
||||
private TextView mPathView;
|
||||
private TextView mRecursiveView;
|
||||
private ImageButton mToggleRecursiveView;
|
||||
private ImageButton mRemoveView;
|
||||
|
||||
public ViewHolder(View rootView) {
|
||||
super(rootView);
|
||||
mPathView = rootView.findViewById(R.id.path);
|
||||
mRecursiveView = rootView.findViewById(R.id.recursive);
|
||||
mToggleRecursiveView = rootView.findViewById(R.id.toggle_recursive);
|
||||
mToggleRecursiveView.setOnClickListener(this);
|
||||
mRemoveView = rootView.findViewById(R.id.remove);
|
||||
mRemoveView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
public void bindData(int position, Entry entry) {
|
||||
mPosition = position;
|
||||
mEntry = entry;
|
||||
updateText();
|
||||
}
|
||||
|
||||
private void updateText() {
|
||||
mPathView.setText(mEntry.getPath());
|
||||
mRecursiveView.setText(getString(mEntry.isRecursive() ? R.string.game_directories_scanning_subdirectories : R.string.game_directories_not_scanning_subdirectories));
|
||||
mToggleRecursiveView.setImageDrawable(getDrawable(mEntry.isRecursive() ? R.drawable.ic_baseline_folder_24 : R.drawable.ic_baseline_folder_open_24));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mToggleRecursiveView == v) {
|
||||
removeSearchDirectory(mContext, mEntry.getPath(), mEntry.isRecursive());
|
||||
mEntry.toggleRecursive();
|
||||
addSearchDirectory(mContext, mEntry.getPath(), mEntry.isRecursive());
|
||||
updateText();
|
||||
} else if (mRemoveView == v) {
|
||||
removeSearchDirectory(mContext, mEntry.getPath(), mEntry.isRecursive());
|
||||
reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
((ViewHolder) holder).bindData(position, mEntries[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return R.layout.layout_game_directory_entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return mEntries[position].getPath().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mEntries.length;
|
||||
}
|
||||
}
|
||||
|
||||
DirectoryListAdapter mDirectoryListAdapter;
|
||||
RecyclerView mRecyclerView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_game_directories);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
mDirectoryListAdapter = new DirectoryListAdapter(this);
|
||||
mRecyclerView = findViewById(R.id.recycler_view);
|
||||
mRecyclerView.setAdapter(mDirectoryListAdapter);
|
||||
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
findViewById(R.id.fab).setOnClickListener((v) -> startAddGameDirectory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public static String getPathFromTreeUri(Context context, Uri treeUri) {
|
||||
String path = FileUtil.getFullPathFromTreeUri(treeUri, context);
|
||||
if (path.length() < 5) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.main_activity_error)
|
||||
.setMessage(R.string.main_activity_get_path_from_directory_error)
|
||||
.setPositiveButton(R.string.main_activity_ok, (dialog, button) -> {
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return null;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static void addSearchDirectory(Context context, String path, boolean recursive) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final String key = recursive ? "GameList/RecursivePaths" : "GameList/Paths";
|
||||
PreferenceHelpers.addToStringList(prefs, key, path);
|
||||
Log.i("GameDirectoriesActivity", "Added path '" + path + "' to game list search directories");
|
||||
}
|
||||
|
||||
public static void removeSearchDirectory(Context context, String path, boolean recursive) {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
final String key = recursive ? "GameList/RecursivePaths" : "GameList/Paths";
|
||||
PreferenceHelpers.removeFromStringList(prefs, key, path);
|
||||
Log.i("GameDirectoriesActivity", "Removed path '" + path + "' from game list search directories");
|
||||
}
|
||||
|
||||
private void startAddGameDirectory() {
|
||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
||||
startActivityForResult(Intent.createChooser(i, getString(R.string.main_activity_choose_directory)),
|
||||
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_ADD_DIRECTORY_TO_GAME_LIST: {
|
||||
if (resultCode != RESULT_OK)
|
||||
return;
|
||||
|
||||
String path = getPathFromTreeUri(this, data.getData());
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
addSearchDirectory(this, path, true);
|
||||
mDirectoryListAdapter.reload();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
private static final int REQUEST_IMPORT_BIOS_IMAGE = 3;
|
||||
private static final int REQUEST_START_FILE = 4;
|
||||
private static final int REQUEST_SETTINGS = 5;
|
||||
private static final int REQUEST_EDIT_GAME_DIRECTORIES = 6;
|
||||
|
||||
private GameList mGameList;
|
||||
private ListView mGameListView;
|
||||
@ -209,8 +210,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
startEmulation(null, false);
|
||||
} else if (id == R.id.action_start_file) {
|
||||
startStartFile();
|
||||
} else if (id == R.id.action_add_game_directory) {
|
||||
startAddGameDirectory();
|
||||
} else if (id == R.id.action_edit_game_directories) {
|
||||
Intent intent = new Intent(this, GameDirectoriesActivity.class);
|
||||
startActivityForResult(intent, REQUEST_EDIT_GAME_DIRECTORIES);
|
||||
return true;
|
||||
} else if (id == R.id.action_scan_for_new_games) {
|
||||
mGameList.refresh(false, false, this);
|
||||
} else if (id == R.id.action_rescan_all_games) {
|
||||
@ -255,22 +258,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
return path;
|
||||
}
|
||||
|
||||
private String getPathFromTreeUri(Uri treeUri) {
|
||||
String path = FileUtil.getFullPathFromTreeUri(treeUri, this);
|
||||
if (path.length() < 5) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.main_activity_error)
|
||||
.setMessage(R.string.main_activity_get_path_from_directory_error)
|
||||
.setPositiveButton(R.string.main_activity_ok, (dialog, button) -> {
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
return null;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
@ -280,20 +267,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (resultCode != RESULT_OK)
|
||||
return;
|
||||
|
||||
String path = getPathFromTreeUri(data.getData());
|
||||
String path = GameDirectoriesActivity.getPathFromTreeUri(this, data.getData());
|
||||
if (path == null)
|
||||
return;
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Set<String> currentValues = prefs.getStringSet("GameList/RecursivePaths", null);
|
||||
if (currentValues == null)
|
||||
currentValues = new HashSet<String>();
|
||||
|
||||
currentValues.add(path);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putStringSet("GameList/RecursivePaths", currentValues);
|
||||
editor.apply();
|
||||
Log.i("MainActivity", "Added path '" + path + "' to game list search directories");
|
||||
GameDirectoriesActivity.addSearchDirectory(this, path, true);
|
||||
mGameList.refresh(false, false, this);
|
||||
}
|
||||
break;
|
||||
@ -322,6 +300,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
loadSettings();
|
||||
}
|
||||
break;
|
||||
|
||||
case REQUEST_EDIT_GAME_DIRECTORIES: {
|
||||
mGameList.refresh(false, false, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,8 +46,14 @@ public class PreferenceHelpers {
|
||||
|
||||
public static boolean addToStringList(SharedPreferences prefs, String keyName, String valueToAdd) {
|
||||
Set<String> values = getStringSet(prefs, keyName);
|
||||
if (values == null)
|
||||
if (values == null) {
|
||||
values = new ArraySet<>();
|
||||
} else {
|
||||
// We need to copy it otherwise the put doesn't save.
|
||||
Set<String> valuesCopy = new ArraySet<>();
|
||||
valuesCopy.addAll(values);
|
||||
values = valuesCopy;
|
||||
}
|
||||
|
||||
final boolean result = values.add(valueToAdd);
|
||||
prefs.edit().putStringSet(keyName, values).commit();
|
||||
@ -59,6 +65,11 @@ public class PreferenceHelpers {
|
||||
if (values == null)
|
||||
return false;
|
||||
|
||||
// We need to copy it otherwise the put doesn't save.
|
||||
Set<String> valuesCopy = new ArraySet<>();
|
||||
valuesCopy.addAll(values);
|
||||
values = valuesCopy;
|
||||
|
||||
final boolean result = values.remove(valueToRemove);
|
||||
prefs.edit().putStringSet(keyName, values).commit();
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user