Adds more ways to add monsters.
This commit is contained in:
@@ -65,7 +65,7 @@ public class CollectionDetailFragment extends MCFragment {
|
||||
FloatingActionButton fab = root.findViewById(R.id.fab_add_monster_to_collection);
|
||||
|
||||
if (fab != null) {
|
||||
fab.setOnClickListener(v -> showAddMonsterDialog());
|
||||
fab.setOnClickListener(v -> showCollectionDetailFabOptionsDialog());
|
||||
}
|
||||
|
||||
mViewModel.getCollection().observe(getViewLifecycleOwner(), collection -> {
|
||||
@@ -134,6 +134,30 @@ public class CollectionDetailFragment extends MCFragment {
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
private void showCollectionDetailFabOptionsDialog() {
|
||||
String[] options = new String[]{
|
||||
getString(R.string.action_add_monster),
|
||||
getString(R.string.action_create_monster),
|
||||
getString(R.string.action_import_monster_from_url),
|
||||
getString(R.string.action_import_monster_from_file)
|
||||
};
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.title_collection_actions)
|
||||
.setItems(options, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
showAddMonsterDialog();
|
||||
} else if (which == 1) {
|
||||
createNewMonster();
|
||||
} else if (which == 2) {
|
||||
showImportUrlDialog();
|
||||
} else if (which == 3) {
|
||||
importMonsterFromFile();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showAddMonsterDialog() {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
mDisposables.add(repository.getMonsters()
|
||||
|
||||
@@ -47,13 +47,13 @@ public class CollectionsFragment extends MCFragment {
|
||||
|
||||
FloatingActionButton fab = root.findViewById(R.id.fab_add_collection);
|
||||
if (fab != null) {
|
||||
fab.setOnClickListener(v -> showCreateCollectionDialog());
|
||||
fab.setOnClickListener(v -> showCollectionFabOptionsDialog());
|
||||
}
|
||||
|
||||
View emptyState = root.findViewById(R.id.empty_state);
|
||||
View emptyStateButton = root.findViewById(R.id.empty_state_button);
|
||||
if (emptyStateButton != null) {
|
||||
emptyStateButton.setOnClickListener(v -> showCreateCollectionDialog());
|
||||
emptyStateButton.setOnClickListener(v -> showCollectionFabOptionsDialog());
|
||||
}
|
||||
|
||||
RecyclerView recyclerView = root.findViewById(R.id.collection_list);
|
||||
@@ -109,6 +109,24 @@ public class CollectionsFragment extends MCFragment {
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void showCollectionFabOptionsDialog() {
|
||||
String[] options = new String[]{
|
||||
getString(R.string.title_new_collection),
|
||||
getString(R.string.action_import_collection)
|
||||
};
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.title_collection_actions)
|
||||
.setItems(options, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
showCreateCollectionDialog();
|
||||
} else if (which == 1) {
|
||||
importCollectionFromFile();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showCreateCollectionDialog() {
|
||||
Context context = requireContext();
|
||||
LinearLayout layout = new LinearLayout(context);
|
||||
|
||||
@@ -148,15 +148,24 @@ public class DashboardFragment extends MCFragment {
|
||||
private void showAddToDashboardOptionsDialog() {
|
||||
String[] options = new String[]{
|
||||
getString(R.string.action_add_single_monster),
|
||||
getString(R.string.action_add_collection_option)
|
||||
getString(R.string.action_add_collection_option),
|
||||
getString(R.string.action_create_monster),
|
||||
getString(R.string.action_import_monster_from_url),
|
||||
getString(R.string.action_import_monster_from_file)
|
||||
};
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.dialog_add_to_dashboard)
|
||||
.setTitle(R.string.title_dashboard_actions)
|
||||
.setItems(options, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
showAddMonsterPicker();
|
||||
} else if (which == 1) {
|
||||
showAddCollectionPicker();
|
||||
} else if (which == 2) {
|
||||
createNewMonster();
|
||||
} else if (which == 3) {
|
||||
showImportUrlDialog();
|
||||
} else if (which == 4) {
|
||||
importMonsterFromFile();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
|
||||
@@ -79,6 +79,9 @@ public class LibraryFragment extends MCFragment {
|
||||
if (item.getItemId() == R.id.menu_action_import_from_url) {
|
||||
showImportUrlDialog();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_action_import_from_file) {
|
||||
importMonsterFromFile();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.menu_action_export_library) {
|
||||
exportLibrary();
|
||||
return true;
|
||||
@@ -98,47 +101,6 @@ public class LibraryFragment extends MCFragment {
|
||||
}, Logger::logError);
|
||||
}
|
||||
|
||||
private void showImportUrlDialog() {
|
||||
Context context = requireContext();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.dialog_import_url_title);
|
||||
builder.setMessage(R.string.dialog_import_url_message);
|
||||
|
||||
final EditText input = new EditText(context);
|
||||
input.setHint(R.string.dialog_import_url_hint);
|
||||
input.setSingleLine(true);
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboard != null && clipboard.hasPrimaryClip()) {
|
||||
ClipData clip = clipboard.getPrimaryClip();
|
||||
if (clip != null && clip.getItemCount() > 0) {
|
||||
CharSequence clipText = clip.getItemAt(0).getText();
|
||||
if (clipText != null) {
|
||||
String clipStr = clipText.toString().trim();
|
||||
if (new DnDBeyondImporter().canImport(clipStr) || new Open5eImporter().canImport(clipStr)) {
|
||||
input.setText(clipStr);
|
||||
input.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton(R.string.dialog_import, (dialog, which) -> {
|
||||
String urlOrId = input.getText().toString().trim();
|
||||
if (!urlOrId.isEmpty()) {
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).importMonsterFromInputAndNavigate(urlOrId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.dialog_cancel, (dialog, which) -> dialog.cancel());
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @Nullable View emptyState) {
|
||||
Context context = requireContext();
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
@@ -185,31 +147,28 @@ public class LibraryFragment extends MCFragment {
|
||||
}
|
||||
|
||||
private void setupAddMonsterButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> createNewMonster());
|
||||
fab.setOnClickListener(view -> showLibraryFabOptionsDialog());
|
||||
}
|
||||
|
||||
private void createNewMonster() {
|
||||
Monster monster = new Monster();
|
||||
monster.name = getString(R.string.default_monster_name);
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
mDisposables.add(repository.addMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeWith(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
navigateToEditMonster(monster.id);
|
||||
private void showLibraryFabOptionsDialog() {
|
||||
String[] options = new String[]{
|
||||
getString(R.string.action_create_monster),
|
||||
getString(R.string.action_import_monster_from_url),
|
||||
getString(R.string.action_import_monster_from_file)
|
||||
};
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.title_library_actions)
|
||||
.setItems(options, (dialog, which) -> {
|
||||
if (which == 0) {
|
||||
createNewMonster();
|
||||
} else if (which == 1) {
|
||||
showImportUrlDialog();
|
||||
} else if (which == 2) {
|
||||
importMonsterFromFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_monster));
|
||||
}
|
||||
}
|
||||
}));
|
||||
})
|
||||
.setNegativeButton(R.string.dialog_cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
protected void navigateToMonsterDetail(@NonNull UUID monsterId) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
@@ -10,27 +12,45 @@ import android.os.Bundle;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
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.fragment.app.Fragment;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.MainActivity;
|
||||
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.importers.DnDBeyondImporter;
|
||||
import com.majinnaibu.monstercards.importers.Open5eImporter;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.SnackbarHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MCFragment extends Fragment {
|
||||
private String mPendingExportContent;
|
||||
private ActivityResultLauncher<Intent> mCreateDocumentLauncher;
|
||||
private ActivityResultLauncher<Intent> mOpenDocumentLauncher;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -46,6 +66,137 @@ public class MCFragment extends Fragment {
|
||||
}
|
||||
mPendingExportContent = null;
|
||||
});
|
||||
|
||||
mOpenDocumentLauncher = registerForActivityResult(
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void importMonsterFromFile() {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
try {
|
||||
mOpenDocumentLauncher.launch(intent);
|
||||
} catch (Exception e) {
|
||||
Logger.logError("Failed to launch file picker", e);
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
SnackbarHelper.showLong(view, R.string.failed_to_import_url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void importCollectionFromFile() {
|
||||
importMonsterFromFile();
|
||||
}
|
||||
|
||||
public void showImportUrlDialog() {
|
||||
Context context = requireContext();
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(R.string.dialog_import_url_title);
|
||||
builder.setMessage(R.string.dialog_import_url_message);
|
||||
|
||||
final EditText input = new EditText(context);
|
||||
input.setHint(R.string.dialog_import_url_hint);
|
||||
input.setSingleLine(true);
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboard != null && clipboard.hasPrimaryClip()) {
|
||||
ClipData clip = clipboard.getPrimaryClip();
|
||||
if (clip != null && clip.getItemCount() > 0) {
|
||||
CharSequence clipText = clip.getItemAt(0).getText();
|
||||
if (clipText != null) {
|
||||
String clipStr = clipText.toString().trim();
|
||||
if (new DnDBeyondImporter().canImport(clipStr) || new Open5eImporter().canImport(clipStr)) {
|
||||
input.setText(clipStr);
|
||||
input.selectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton(R.string.dialog_import, (dialog, which) -> {
|
||||
String urlOrId = input.getText().toString().trim();
|
||||
if (!urlOrId.isEmpty()) {
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).importMonsterFromInputAndNavigate(urlOrId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.dialog_cancel, (dialog, which) -> dialog.cancel());
|
||||
|
||||
builder.show();
|
||||
}
|
||||
|
||||
public void createNewMonster() {
|
||||
Monster monster = new Monster();
|
||||
monster.name = getString(R.string.default_monster_name);
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
repository.addMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
navigateToEditMonster(monster.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
SnackbarHelper.showLong(view, getString(R.string.snackbar_failed_to_create_monster));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditMonster(@NonNull UUID monsterId) {
|
||||
try {
|
||||
NavController navController = Navigation.findNavController(requireView());
|
||||
Bundle args = new Bundle();
|
||||
args.putString("monster_id", monsterId.toString());
|
||||
navController.navigate(R.id.edit_monster_navigation, args);
|
||||
} catch (Exception e) {
|
||||
Logger.logError("Error navigating to edit monster", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String readContentsFromUri(@NonNull Uri uri) {
|
||||
Context context = getContext();
|
||||
if (context == null) return null;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(inputStream), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line).append("\n");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.logError("Error reading URI contents for import", e);
|
||||
return null;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public MonsterCardsApplication getApplication() {
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
android:title="@string/action_import_from_url"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_action_import_from_file"
|
||||
android:title="@string/action_import_monster_from_file"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_action_export_library"
|
||||
android:title="@string/action_export_library"
|
||||
|
||||
@@ -198,4 +198,12 @@
|
||||
<string name="snackbar_dashboard_already_empty">Dashboard is already empty</string>
|
||||
<string name="dialog_remove_from_dashboard_title">Remove %1$s?</string>
|
||||
<string name="dialog_remove_from_dashboard_message">Remove %1$s from your dashboard?</string>
|
||||
|
||||
<string name="action_create_monster">Create Monster</string>
|
||||
<string name="action_import_monster_from_url">Import Monster from Url</string>
|
||||
<string name="action_import_monster_from_file">Import Monster from File</string>
|
||||
<string name="action_import_collection">Import Collection</string>
|
||||
<string name="title_library_actions">Library Actions</string>
|
||||
<string name="title_collection_actions">Collection Actions</string>
|
||||
<string name="title_dashboard_actions">Dashboard Actions</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user