Adds confirmation when going up from the edit monster screen to save, cancel, or discard changes.

This commit is contained in:
2021-05-27 19:16:05 -07:00
parent ced5560dd0
commit f41b8d0065
5 changed files with 113 additions and 21 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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);
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 {

View File

@@ -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;
}
}