Adds confirmation when going up from the edit monster screen to save, cancel, or discard changes.
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
@@ -12,13 +13,19 @@ import androidx.navigation.ui.NavigationUI;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
||||
import com.majinnaibu.monstercards.init.FlipperInitializer;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
|
||||
return navController.navigateUp() || super.onSupportNavigateUp();
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
Logger.logDebug("Home selected (in MainActivity)");
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -44,5 +51,4 @@ public class MainActivity extends AppCompatActivity {
|
||||
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
|
||||
NavigationUI.setupWithNavController(navView, navController);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package com.majinnaibu.monstercards.data;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
@@ -30,6 +31,9 @@ public interface MonsterDAO {
|
||||
@Insert
|
||||
Completable insertAll(Monster... monsters);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
Completable save(Monster... monsters);
|
||||
|
||||
@Delete
|
||||
Completable delete(Monster monster);
|
||||
}
|
||||
|
||||
@@ -63,4 +63,9 @@ public class MonsterRepository {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable saveMonster(Monster monster) {
|
||||
Completable result = m_db.monsterDAO().save(monster);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,17 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
@@ -21,19 +24,20 @@ import com.majinnaibu.monstercards.ui.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentArgs;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
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.observers.DisposableSingleObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
public class EditMonsterFragment extends MCFragment {
|
||||
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
public static EditMonsterFragment newInstance() {
|
||||
return new EditMonsterFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
@@ -49,20 +53,20 @@ public class EditMonsterFragment extends MCFragment {
|
||||
|
||||
View root = inflater.inflate(R.layout.fragment_edit_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
requireAppCompatActivity().getSupportActionBar().setTitle(getString(R.string.title_edit_monster, getString(R.string.default_monster_name)));
|
||||
|
||||
setTitle(getString(R.string.title_edit_monster, getString(R.string.default_monster_name)));
|
||||
|
||||
// TODO: Show a loading spinner until we have the monster loaded.
|
||||
if (mViewModel.hasError() || !mViewModel.hasLoaded() || !mViewModel.getMonsterId().getValue().equals(monsterId)) {
|
||||
if (mViewModel.hasError() || !mViewModel.hasLoaded() || !Objects.equals(mViewModel.getMonsterId().getValue(), monsterId)) {
|
||||
repository.getMonster(monsterId).toObservable()
|
||||
.firstOrError()
|
||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||
Logger.logDebug(String.format("Monster loaded: %s", monster.name));
|
||||
mViewModel.setHasLoaded(true);
|
||||
mViewModel.setHasError(false);
|
||||
mViewModel.copyFromMonster(monster);
|
||||
requireAppCompatActivity().getSupportActionBar().setTitle(getString(R.string.title_edit_monster, monster.name));
|
||||
setTitle(getString(R.string.title_edit_monster, monster.name));
|
||||
dispose();
|
||||
}
|
||||
|
||||
@@ -77,20 +81,57 @@ public class EditMonsterFragment extends MCFragment {
|
||||
});
|
||||
}
|
||||
mHolder.basicInfoButton.setOnClickListener(v -> {
|
||||
// TODO: Navigate to the EditBasicInfo fragment
|
||||
Logger.logDebug("Basic Info clicked");
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditBasicInfoFragment();
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Navigation.findNavController(view).navigate(action);
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
View view = getView();
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(requireContext()).create();
|
||||
alertDialog.setTitle("Unsaved Changes");
|
||||
alertDialog.setMessage("Do you want to save your changes?");
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "Save", (dialog, id) -> {
|
||||
// Save the monster. Navigate up if the save is successful. Show a SnackBar if there was an error.
|
||||
getMonsterRepository().saveMonster(mViewModel.buildMonster())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
assert view != null;
|
||||
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Discard", (dialog, id) -> {
|
||||
// Navigate up ignoring unsaved changes.
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
});
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Cancel", (dialog, id) -> {
|
||||
// Do nothing.
|
||||
});
|
||||
alertDialog.show();
|
||||
} else {
|
||||
// No changes so we can safely leave.
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@SuppressWarnings({"ConstantConditions", "unused"})
|
||||
public class EditMonsterViewModel extends ViewModel {
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
@@ -20,6 +21,7 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
private final MutableLiveData<String> mSubtype;
|
||||
private final MutableLiveData<String> mAlignment;
|
||||
private final MutableLiveData<String> mCustomHitPoints;
|
||||
private final MutableLiveData<Boolean> mHasChanges;
|
||||
|
||||
public EditMonsterViewModel() {
|
||||
|
||||
@@ -33,6 +35,8 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
mSubtype = new MutableLiveData<>("");
|
||||
mAlignment = new MutableLiveData<>("");
|
||||
mCustomHitPoints = new MutableLiveData<>("");
|
||||
// TODO: consider initializing this to true so all new monsters need saving
|
||||
mHasChanges = new MutableLiveData<>(false);
|
||||
}
|
||||
|
||||
public void copyFromMonster(Monster monster) {
|
||||
@@ -44,6 +48,7 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
mSubtype.setValue(monster.subtype);
|
||||
mAlignment.setValue(monster.alignment);
|
||||
mCustomHitPoints.setValue(monster.customHPDescription);
|
||||
mHasChanges.setValue(false);
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
@@ -52,9 +57,9 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
|
||||
public void setName(@NonNull String name) {
|
||||
mName.setValue(name);
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
|
||||
public LiveData<UUID> getMonsterId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
@@ -97,6 +102,7 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
|
||||
public void setSize(@NonNull String size) {
|
||||
mSize.setValue(size);
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
public LiveData<String> getType() {
|
||||
@@ -105,6 +111,7 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
|
||||
public void setType(@NonNull String type) {
|
||||
mType.setValue(type);
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
public LiveData<String> getSubtype() {
|
||||
@@ -113,6 +120,7 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
|
||||
public void setSubtype(@NonNull String subType) {
|
||||
mSubtype.setValue(subType);
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
public LiveData<String> getAlignment() {
|
||||
@@ -121,6 +129,7 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
|
||||
public void setAlignment(@NonNull String alignment) {
|
||||
mAlignment.setValue(alignment);
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
public LiveData<String> getCustomHitPoints() {
|
||||
@@ -129,5 +138,32 @@ public class EditMonsterViewModel extends ViewModel {
|
||||
|
||||
public void setCustomHitPoints(String customHitPoints) {
|
||||
mCustomHitPoints.setValue(customHitPoints);
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getHasChanges() {
|
||||
return mHasChanges;
|
||||
}
|
||||
|
||||
public void setHasChanges(@NonNull Boolean hasChanges) {
|
||||
mHasChanges.setValue(hasChanges);
|
||||
}
|
||||
|
||||
public boolean hasChanges() {
|
||||
return mHasChanges.getValue();
|
||||
}
|
||||
|
||||
public Monster buildMonster() {
|
||||
Monster monster = new Monster();
|
||||
|
||||
monster.id = mMonsterId.getValue();
|
||||
monster.name = mName.getValue();
|
||||
monster.size = mSize.getValue();
|
||||
monster.type = mType.getValue();
|
||||
monster.subtype = mSubtype.getValue();
|
||||
monster.alignment = mAlignment.getValue();
|
||||
monster.customHPDescription = mCustomHitPoints.getValue();
|
||||
|
||||
return monster;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user