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;
|
package com.majinnaibu.monstercards;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.Navigation;
|
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.navigation.ui.AppBarConfiguration;
|
import androidx.navigation.ui.AppBarConfiguration;
|
||||||
import androidx.navigation.ui.NavigationUI;
|
import androidx.navigation.ui.NavigationUI;
|
||||||
@@ -12,13 +13,19 @@ import androidx.navigation.ui.NavigationUI;
|
|||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
||||||
import com.majinnaibu.monstercards.init.FlipperInitializer;
|
import com.majinnaibu.monstercards.init.FlipperInitializer;
|
||||||
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSupportNavigateUp() {
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
|
if (item.getItemId() == android.R.id.home) {
|
||||||
return navController.navigateUp() || super.onSupportNavigateUp();
|
Logger.logDebug("Home selected (in MainActivity)");
|
||||||
|
getOnBackPressedDispatcher().onBackPressed();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@@ -44,5 +51,4 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
|
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
|
||||||
NavigationUI.setupWithNavController(navView, navController);
|
NavigationUI.setupWithNavController(navView, navController);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ package com.majinnaibu.monstercards.data;
|
|||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Delete;
|
import androidx.room.Delete;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
|
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
@@ -30,6 +31,9 @@ public interface MonsterDAO {
|
|||||||
@Insert
|
@Insert
|
||||||
Completable insertAll(Monster... monsters);
|
Completable insertAll(Monster... monsters);
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
Completable save(Monster... monsters);
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
Completable delete(Monster monster);
|
Completable delete(Monster monster);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,4 +63,9 @@ public class MonsterRepository {
|
|||||||
return result;
|
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.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.activity.OnBackPressedCallback;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.NavBackStackEntry;
|
import androidx.navigation.NavBackStackEntry;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.NavDirections;
|
import androidx.navigation.NavDirections;
|
||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
|
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.majinnaibu.monstercards.R;
|
import com.majinnaibu.monstercards.R;
|
||||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
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.ui.monster.MonsterDetailFragmentArgs;
|
||||||
import com.majinnaibu.monstercards.utils.Logger;
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
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.observers.DisposableSingleObserver;
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||||
|
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
public class EditMonsterFragment extends MCFragment {
|
public class EditMonsterFragment extends MCFragment {
|
||||||
|
|
||||||
private EditMonsterViewModel mViewModel;
|
private EditMonsterViewModel mViewModel;
|
||||||
private ViewHolder mHolder;
|
private ViewHolder mHolder;
|
||||||
|
|
||||||
public static EditMonsterFragment newInstance() {
|
|
||||||
return new EditMonsterFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
@@ -49,20 +53,20 @@ public class EditMonsterFragment extends MCFragment {
|
|||||||
|
|
||||||
View root = inflater.inflate(R.layout.fragment_edit_monster, container, false);
|
View root = inflater.inflate(R.layout.fragment_edit_monster, container, false);
|
||||||
mHolder = new ViewHolder(root);
|
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.
|
// 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()
|
repository.getMonster(monsterId).toObservable()
|
||||||
.firstOrError()
|
.firstOrError()
|
||||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||||
Logger.logDebug(String.format("Monster loaded: %s", monster.name));
|
|
||||||
mViewModel.setHasLoaded(true);
|
mViewModel.setHasLoaded(true);
|
||||||
mViewModel.setHasError(false);
|
mViewModel.setHasError(false);
|
||||||
mViewModel.copyFromMonster(monster);
|
mViewModel.copyFromMonster(monster);
|
||||||
requireAppCompatActivity().getSupportActionBar().setTitle(getString(R.string.title_edit_monster, monster.name));
|
setTitle(getString(R.string.title_edit_monster, monster.name));
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,20 +81,57 @@ public class EditMonsterFragment extends MCFragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
mHolder.basicInfoButton.setOnClickListener(v -> {
|
mHolder.basicInfoButton.setOnClickListener(v -> {
|
||||||
// TODO: Navigate to the EditBasicInfo fragment
|
|
||||||
Logger.logDebug("Basic Info clicked");
|
|
||||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditBasicInfoFragment();
|
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditBasicInfoFragment();
|
||||||
View view = getView();
|
View view = getView();
|
||||||
assert view != null;
|
assert view != null;
|
||||||
Navigation.findNavController(view).navigate(action);
|
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
|
@Override
|
||||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
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 {
|
private static class ViewHolder {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.majinnaibu.monstercards.models.Monster;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@SuppressWarnings({"ConstantConditions", "unused"})
|
||||||
public class EditMonsterViewModel extends ViewModel {
|
public class EditMonsterViewModel extends ViewModel {
|
||||||
private final MutableLiveData<String> mName;
|
private final MutableLiveData<String> mName;
|
||||||
private final MutableLiveData<UUID> mMonsterId;
|
private final MutableLiveData<UUID> mMonsterId;
|
||||||
@@ -20,6 +21,7 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
private final MutableLiveData<String> mSubtype;
|
private final MutableLiveData<String> mSubtype;
|
||||||
private final MutableLiveData<String> mAlignment;
|
private final MutableLiveData<String> mAlignment;
|
||||||
private final MutableLiveData<String> mCustomHitPoints;
|
private final MutableLiveData<String> mCustomHitPoints;
|
||||||
|
private final MutableLiveData<Boolean> mHasChanges;
|
||||||
|
|
||||||
public EditMonsterViewModel() {
|
public EditMonsterViewModel() {
|
||||||
|
|
||||||
@@ -33,6 +35,8 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
mSubtype = new MutableLiveData<>("");
|
mSubtype = new MutableLiveData<>("");
|
||||||
mAlignment = new MutableLiveData<>("");
|
mAlignment = new MutableLiveData<>("");
|
||||||
mCustomHitPoints = 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) {
|
public void copyFromMonster(Monster monster) {
|
||||||
@@ -44,6 +48,7 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
mSubtype.setValue(monster.subtype);
|
mSubtype.setValue(monster.subtype);
|
||||||
mAlignment.setValue(monster.alignment);
|
mAlignment.setValue(monster.alignment);
|
||||||
mCustomHitPoints.setValue(monster.customHPDescription);
|
mCustomHitPoints.setValue(monster.customHPDescription);
|
||||||
|
mHasChanges.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<String> getName() {
|
public LiveData<String> getName() {
|
||||||
@@ -52,9 +57,9 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setName(@NonNull String name) {
|
public void setName(@NonNull String name) {
|
||||||
mName.setValue(name);
|
mName.setValue(name);
|
||||||
|
mHasChanges.setValue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LiveData<UUID> getMonsterId() {
|
public LiveData<UUID> getMonsterId() {
|
||||||
return mMonsterId;
|
return mMonsterId;
|
||||||
}
|
}
|
||||||
@@ -97,6 +102,7 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setSize(@NonNull String size) {
|
public void setSize(@NonNull String size) {
|
||||||
mSize.setValue(size);
|
mSize.setValue(size);
|
||||||
|
mHasChanges.setValue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<String> getType() {
|
public LiveData<String> getType() {
|
||||||
@@ -105,6 +111,7 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setType(@NonNull String type) {
|
public void setType(@NonNull String type) {
|
||||||
mType.setValue(type);
|
mType.setValue(type);
|
||||||
|
mHasChanges.setValue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<String> getSubtype() {
|
public LiveData<String> getSubtype() {
|
||||||
@@ -113,6 +120,7 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setSubtype(@NonNull String subType) {
|
public void setSubtype(@NonNull String subType) {
|
||||||
mSubtype.setValue(subType);
|
mSubtype.setValue(subType);
|
||||||
|
mHasChanges.setValue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<String> getAlignment() {
|
public LiveData<String> getAlignment() {
|
||||||
@@ -121,6 +129,7 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setAlignment(@NonNull String alignment) {
|
public void setAlignment(@NonNull String alignment) {
|
||||||
mAlignment.setValue(alignment);
|
mAlignment.setValue(alignment);
|
||||||
|
mHasChanges.setValue(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<String> getCustomHitPoints() {
|
public LiveData<String> getCustomHitPoints() {
|
||||||
@@ -129,5 +138,32 @@ public class EditMonsterViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setCustomHitPoints(String customHitPoints) {
|
public void setCustomHitPoints(String customHitPoints) {
|
||||||
mCustomHitPoints.setValue(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