Cleans up import and export options.

This commit is contained in:
2026-09-20 19:10:43 -07:00
parent 77176a6d4f
commit a3998d6c1d
31 changed files with 2280 additions and 110 deletions

View File

@@ -221,10 +221,32 @@ public class CollectionDetailFragment extends MCFragment {
} else if (item.getItemId() == R.id.menu_action_export_collection) {
exportCollection();
return true;
} else if (item.getItemId() == R.id.menu_action_remove_all_monsters_from_collection) {
showRemoveAllMonstersConfirmationDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showRemoveAllMonstersConfirmationDialog() {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_remove_all_from_collection_title)
.setMessage(R.string.dialog_remove_all_from_collection_message)
.setPositiveButton(R.string.action_remove_all, (dialog, which) -> {
mDisposables.add(getMonsterRepository().removeAllMonstersFromCollection(mCollectionId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
SnackbarHelper.showLong(view, R.string.snackbar_collection_cleared);
}
}, throwable -> Logger.logError("Failed to remove all monsters from collection", throwable)));
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}
private void exportCollection() {
Collection collection = mViewModel.getCollection().getValue();
List<Monster> monsters = mViewModel.getMonsters().getValue();

View File

@@ -4,6 +4,9 @@ import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
@@ -43,6 +46,7 @@ public class CollectionsFragment extends MCFragment {
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
setHasOptionsMenu(true);
View root = inflater.inflate(R.layout.fragment_collections, container, false);
FloatingActionButton fab = root.findViewById(R.id.fab_add_collection);
@@ -190,6 +194,51 @@ public class CollectionsFragment extends MCFragment {
Navigation.findNavController(requireView()).navigate(action);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.collections_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.menu_action_export_collections) {
exportCollections();
return true;
} else if (item.getItemId() == R.id.menu_action_remove_all_collections) {
showRemoveAllCollectionsConfirmationDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void exportCollections() {
mDisposables.add(getMonsterRepository().exportCollections()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(json -> exportToFile("collections.binder", json),
throwable -> Logger.logError("Failed to export collections", throwable)));
}
private void showRemoveAllCollectionsConfirmationDialog() {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_remove_all_collections_title)
.setMessage(R.string.dialog_remove_all_collections_message)
.setPositiveButton(R.string.action_remove_all, (dialog, which) -> {
mDisposables.add(getMonsterRepository().removeAllCollections()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
SnackbarHelper.showLong(view, R.string.snackbar_all_collections_removed);
}
}, throwable -> Logger.logError("Failed to remove all collections", throwable)));
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}
@Override
public void onDestroyView() {
super.onDestroyView();

View File

@@ -24,6 +24,7 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Collection;
import com.majinnaibu.monstercards.models.CollectionWithCount;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
@@ -265,7 +266,7 @@ public class DashboardFragment extends MCFragment {
private void showAddCollectionPicker() {
MonsterRepository repository = getMonsterRepository();
mDisposables.add(repository.getCollections()
mDisposables.add(repository.getCollectionsWithCount()
.firstOrError()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@@ -279,12 +280,16 @@ public class DashboardFragment extends MCFragment {
}
String[] names = new String[collections.size()];
for (int i = 0; i < collections.size(); i++) {
names[i] = collections.get(i).name;
CollectionWithCount item = collections.get(i);
int count = item.monsterCount;
String countText = count == 1 ? "1 monster" : count + " monsters";
String colName = item.collection != null && item.collection.name != null ? item.collection.name : "";
names[i] = colName + " (" + countText + ")";
}
new AlertDialog.Builder(requireContext())
.setTitle(R.string.action_add_collection_option)
.setItems(names, (dialog, which) -> {
Collection selected = collections.get(which);
Collection selected = collections.get(which).collection;
addCollectionToDashboard(selected);
})
.setNegativeButton(R.string.dialog_cancel, null)
@@ -365,9 +370,6 @@ public class DashboardFragment extends MCFragment {
} else if (item.getItemId() == R.id.menu_action_add_collection_option) {
showAddCollectionPicker();
return true;
} else if (item.getItemId() == R.id.menu_action_remove_single_monster) {
showRemoveMonsterPicker();
return true;
} else if (item.getItemId() == R.id.menu_action_clear_dashboard) {
clearDashboard();
return true;

View File

@@ -85,10 +85,35 @@ public class LibraryFragment extends MCFragment {
} else if (item.getItemId() == R.id.menu_action_export_library) {
exportLibrary();
return true;
} else if (item.getItemId() == R.id.menu_action_export_everything) {
exportEverything();
return true;
} else if (item.getItemId() == R.id.menu_action_clear_all_data) {
showClearAllDataConfirmationDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showClearAllDataConfirmationDialog() {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.dialog_clear_all_data_title)
.setMessage(R.string.dialog_clear_all_data_message)
.setPositiveButton(R.string.action_clear, (dialog, which) -> {
mDisposables.add(getMonsterRepository().clearAllData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {
View view = getView();
if (view != null) {
SnackbarHelper.showLong(view, R.string.snackbar_all_data_cleared);
}
}, throwable -> Logger.logError("Failed to clear all data", throwable)));
})
.setNegativeButton(R.string.dialog_cancel, null)
.show();
}
private void exportLibrary() {
getMonsterRepository().getMonsters()
.firstOrError()
@@ -101,6 +126,16 @@ public class LibraryFragment extends MCFragment {
}, Logger::logError);
}
private void exportEverything() {
mDisposables.add(getMonsterRepository().exportEverything()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(json -> {
String fileName = getString(R.string.default_filename_export_everything) + ".binder";
exportToFile(fileName, json);
}, throwable -> Logger.logError("Failed to export everything", throwable)));
}
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @Nullable View emptyState) {
Context context = requireContext();
MonsterRepository repository = this.getMonsterRepository();

View File

@@ -40,6 +40,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@@ -71,17 +73,22 @@ public class MCFragment extends Fragment {
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
Uri uri = result.getData().getData();
if (uri != null && getActivity() instanceof MainActivity) {
String content = readContentsFromUri(uri);
if (content != null && !content.trim().isEmpty()) {
((MainActivity) getActivity()).importMonsterFromInputAndNavigate(content);
} else {
View view = getView();
if (view != null) {
SnackbarHelper.showLong(view, R.string.failed_to_import_url);
Intent data = result.getData();
List<Uri> uris = new ArrayList<>();
if (data.getClipData() != null) {
ClipData clipData = data.getClipData();
for (int i = 0; i < clipData.getItemCount(); i++) {
Uri uri = clipData.getItemAt(i).getUri();
if (uri != null) {
uris.add(uri);
}
}
} else if (data.getData() != null) {
uris.add(data.getData());
}
if (!uris.isEmpty() && getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).importMultipleFilesFromUris(uris);
}
}
});
@@ -91,6 +98,7 @@ public class MCFragment extends Fragment {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
try {
mOpenDocumentLauncher.launch(intent);
} catch (Exception e) {