diff --git a/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java b/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java index d8f7b69..4532471 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java +++ b/app/src/main/java/com/majinnaibu/monstercards/MainActivity.java @@ -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); } - } \ No newline at end of file diff --git a/app/src/main/java/com/majinnaibu/monstercards/data/MonsterDAO.java b/app/src/main/java/com/majinnaibu/monstercards/data/MonsterDAO.java index a622683..9671790 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/data/MonsterDAO.java +++ b/app/src/main/java/com/majinnaibu/monstercards/data/MonsterDAO.java @@ -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); } diff --git a/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java b/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java index 56383bf..c16a836 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java +++ b/app/src/main/java/com/majinnaibu/monstercards/data/MonsterRepository.java @@ -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; + } } diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java index af63ae3..a011985 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterFragment.java @@ -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() { @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 { diff --git a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java index a953409..14a6e07 100644 --- a/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java +++ b/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditMonsterViewModel.java @@ -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 mName; private final MutableLiveData mMonsterId; @@ -20,6 +21,7 @@ public class EditMonsterViewModel extends ViewModel { private final MutableLiveData mSubtype; private final MutableLiveData mAlignment; private final MutableLiveData mCustomHitPoints; + private final MutableLiveData 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 getName() { @@ -52,9 +57,9 @@ public class EditMonsterViewModel extends ViewModel { public void setName(@NonNull String name) { mName.setValue(name); + mHasChanges.setValue(true); } - public LiveData 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 getType() { @@ -105,6 +111,7 @@ public class EditMonsterViewModel extends ViewModel { public void setType(@NonNull String type) { mType.setValue(type); + mHasChanges.setValue(true); } public LiveData getSubtype() { @@ -113,6 +120,7 @@ public class EditMonsterViewModel extends ViewModel { public void setSubtype(@NonNull String subType) { mSubtype.setValue(subType); + mHasChanges.setValue(true); } public LiveData getAlignment() { @@ -121,6 +129,7 @@ public class EditMonsterViewModel extends ViewModel { public void setAlignment(@NonNull String alignment) { mAlignment.setValue(alignment); + mHasChanges.setValue(true); } public LiveData getCustomHitPoints() { @@ -129,5 +138,32 @@ public class EditMonsterViewModel extends ViewModel { public void setCustomHitPoints(String customHitPoints) { mCustomHitPoints.setValue(customHitPoints); + mHasChanges.setValue(true); + } + + public LiveData 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; } }