Adds edit basic info screen with most string fields.

Cleans up fonts/margins on edit screens.
Makes the EditMonsterViewModel shared between edit monster fragments.
This commit is contained in:
2021-05-27 04:49:26 -07:00
committed by Tom Hicks
parent 0db46ebb51
commit e075fc4369
8 changed files with 293 additions and 65 deletions

View File

@@ -6,60 +6,70 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.ui.components.Stepper;
import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.ui.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import com.majinnaibu.monstercards.utils.TextChangedListener;
/**
* A simple {@link Fragment} subclass.
*/
public class EditBasicInfoFragment extends MCFragment {
private EditMonsterViewModel mViewModel;
private ViewHolder mHolder;
@Override
public void onStart() {
super.onStart();
mHolder.name.requestFocus();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_basic_info, container, false);
mHolder = new ViewHolder(root);
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_edit_basic_info, container, false);
mHolder = new ViewHolder(root);
mHolder.name.setText(mViewModel.getName().getValue());
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> {
mViewModel.setName(s.toString());
Logger.logDebug(String.format("Monster Name changed to %s", mViewModel.getName().getValue()));
}));
mHolder.size.setText(mViewModel.getSize().getValue());
mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSize(s.toString())));
mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> {
mViewModel.setSize(s.toString());
Logger.logDebug(String.format("Monster Size changed to %s", mViewModel.getSize().getValue()));
}));
mHolder.type.setText(mViewModel.getType().getValue());
mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setType(s.toString())));
mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> {
mViewModel.setType(s.toString());
Logger.logDebug(String.format("Monster Type changed to %s", mViewModel.getType().getValue()));
}));
mHolder.subtype.setText(mViewModel.getSubtype().getValue());
mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSubtype(s.toString())));
mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> {
mViewModel.setSubtype(s.toString());
Logger.logDebug(String.format("Monster Subtype changed to %s", mViewModel.getSubtype().getValue()));
}));
mHolder.alignment.setText(mViewModel.getAlignment().getValue());
mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setAlignment(s.toString())));
mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> {
mViewModel.setAlignment(s.toString());
Logger.logDebug(String.format("Monster Alignment changed to %s", mViewModel.getAlignment().getValue()));
}));
mHolder.customHitPoints.setText(mViewModel.getCustomHitPoints().getValue());
mHolder.customHitPoints.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomHitPoints(s.toString()))));
mHolder.hitDice.setValue(mViewModel.getHitDiceUnboxed());
mHolder.hitDice.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setHitDice(newValue));
mHolder.hasCustomHitPoints.setChecked(mViewModel.getHasCustomHitPointsValueAsBoolean());
mHolder.hasCustomHitPoints.setOnCheckedChangeListener((button, isChecked) -> mViewModel.setHasCustomHitPoints(isChecked));
mHolder.customHitPoints.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> {
mViewModel.setCustomHitPoints(s.toString());
Logger.logDebug(String.format("Monster Custom Hit Points changed to %s", mViewModel.getCustomHitPoints().getValue()));
})));
return root;
}
@@ -71,18 +81,15 @@ public class EditBasicInfoFragment extends MCFragment {
private final EditText subtype;
private final EditText alignment;
private final EditText customHitPoints;
private final Stepper hitDice;
private final SwitchMaterial hasCustomHitPoints;
ViewHolder(@NonNull View root) {
ViewHolder(View root) {
name = root.findViewById(R.id.name);
size = root.findViewById(R.id.size);
type = root.findViewById(R.id.type);
subtype = root.findViewById(R.id.subtype);
alignment = root.findViewById(R.id.alignment);
customHitPoints = root.findViewById(R.id.customHitPoints);
hitDice = root.findViewById(R.id.hitDice);
hasCustomHitPoints = root.findViewById(R.id.hasCustomHitPoints);
// TODO: add hitDice, hasCustomHitPoints, and customHitPoints
}
}
}

View File

@@ -4,17 +4,31 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.MCFragment;
import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentArgs;
import com.majinnaibu.monstercards.utils.Logger;
public class EditMonsterFragment extends Fragment {
import java.util.UUID;
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
public class EditMonsterFragment extends MCFragment {
private EditMonsterViewModel mViewModel;
private ViewHolder mHolder;
public static EditMonsterFragment newInstance() {
return new EditMonsterFragment();
@@ -23,14 +37,68 @@ public class EditMonsterFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_edit_monster, container, false);
MonsterRepository repository = getMonsterRepository();
Bundle arguments = getArguments();
assert arguments != null;
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
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)));
// TODO: Show a loading spinner until we have the monster loaded.
if (mViewModel.hasError() || !mViewModel.hasLoaded() || !mViewModel.getMonsterId().getValue().equals(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));
dispose();
}
@Override
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
// TODO: Show an error state.
Logger.logError(e);
mViewModel.setHasError(true);
mViewModel.setErrorMessage(e.toString());
dispose();
}
});
}
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;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(EditMonsterViewModel.class);
// TODO: Use the ViewModel
}
private static class ViewHolder {
TextView basicInfoButton;
ViewHolder(View root) {
basicInfoButton = root.findViewById(R.id.basicInfo);
}
}
}

View File

@@ -1,7 +1,133 @@
package com.majinnaibu.monstercards.ui.editmonster;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.majinnaibu.monstercards.models.Monster;
import java.util.UUID;
public class EditMonsterViewModel extends ViewModel {
// TODO: Implement the ViewModel
private final MutableLiveData<String> mName;
private final MutableLiveData<UUID> mMonsterId;
private final MutableLiveData<String> mErrorMessage;
private final MutableLiveData<Boolean> mHasError;
private final MutableLiveData<Boolean> mHasLoaded;
private final MutableLiveData<String> mSize;
private final MutableLiveData<String> mType;
private final MutableLiveData<String> mSubtype;
private final MutableLiveData<String> mAlignment;
private final MutableLiveData<String> mCustomHitPoints;
public EditMonsterViewModel() {
mName = new MutableLiveData<>("");
mMonsterId = new MutableLiveData<>(UUID.randomUUID());
mErrorMessage = new MutableLiveData<>("");
mHasError = new MutableLiveData<>(false);
mHasLoaded = new MutableLiveData<>(false);
mSize = new MutableLiveData<>("");
mType = new MutableLiveData<>("");
mSubtype = new MutableLiveData<>("");
mAlignment = new MutableLiveData<>("");
mCustomHitPoints = new MutableLiveData<>("");
}
public void copyFromMonster(Monster monster) {
// TODO: copy from monster to other fields
mMonsterId.setValue(monster.id);
mName.setValue(monster.name);
mSize.setValue(monster.size);
mType.setValue(monster.type);
mSubtype.setValue(monster.subtype);
mAlignment.setValue(monster.alignment);
mCustomHitPoints.setValue(monster.customHPDescription);
}
public LiveData<String> getName() {
return mName;
}
public void setName(@NonNull String name) {
mName.setValue(name);
}
public LiveData<UUID> getMonsterId() {
return mMonsterId;
}
public LiveData<String> getErrorMessage() {
return mErrorMessage;
}
public void setErrorMessage(@NonNull String errorMessage) {
mErrorMessage.setValue(errorMessage);
}
public LiveData<Boolean> getHasError() {
return mHasError;
}
public void setHasError(@NonNull Boolean hasError) {
mHasError.setValue(hasError);
}
public boolean hasError() {
return getHasError().getValue();
}
public LiveData<Boolean> getHasLoaded() {
return mHasLoaded;
}
public void setHasLoaded(@NonNull Boolean hasLoaded) {
mHasLoaded.setValue(hasLoaded);
}
public boolean hasLoaded() {
return getHasLoaded().getValue();
}
public LiveData<String> getSize() {
return mSize;
}
public void setSize(@NonNull String size) {
mSize.setValue(size);
}
public LiveData<String> getType() {
return mType;
}
public void setType(@NonNull String type) {
mType.setValue(type);
}
public LiveData<String> getSubtype() {
return mSubtype;
}
public void setSubtype(@NonNull String subType) {
mSubtype.setValue(subType);
}
public LiveData<String> getAlignment() {
return mAlignment;
}
public void setAlignment(@NonNull String alignment) {
mAlignment.setValue(alignment);
}
public LiveData<String> getCustomHitPoints() {
return mCustomHitPoints;
}
public void setCustomHitPoints(String customHitPoints) {
mCustomHitPoints.setValue(customHitPoints);
}
}

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -20,8 +19,6 @@
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="@string/label_name"
android:importantForAutofill="no"
android:inputType="textCapWords"
@@ -89,23 +86,8 @@
</com.google.android.material.textfield.TextInputLayout>
<!-- Hit Dice (int) -->
<com.majinnaibu.monstercards.ui.components.Stepper
android:id="@+id/hitDice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
app:label="@string/label_hit_dice"
app:maxValue="99"
app:minValue="1"
app:stepAmount="1" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/hasCustomHitPoints"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_has_custom_hp"
android:textAppearance="@android:style/TextAppearance.Material.Medium" />
<!-- Has ustom HP (boolean) -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"

View File

@@ -6,7 +6,6 @@
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="?android:attr/dividerVertical"
@@ -21,6 +20,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_basic_info"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -29,6 +29,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_armor"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -37,6 +38,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_speed"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -45,6 +47,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_ability_scores"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -53,6 +56,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_saving_throws"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -61,6 +65,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_skills"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -69,6 +74,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_condition_immunities"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -77,6 +83,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_damage_immunities"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -85,6 +92,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_damage_resistances"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -93,6 +101,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_damage_vulnerabilities"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -101,6 +110,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_senses"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -109,6 +119,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_languages"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -117,6 +128,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_challenge_rating"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -125,6 +137,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_abilities"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -133,6 +146,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_actions"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -141,6 +155,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_reactions"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -149,6 +164,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_legendary_actions"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -157,6 +173,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_lair_actions"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
<TextView
@@ -165,6 +182,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:text="@string/label_regional_actions"
android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" />
</LinearLayout>

View File

@@ -55,16 +55,32 @@
app:argType="string" />
<action
android:id="@+id/action_navigation_monster_to_editMonsterFragment"
app:destination="@id/editMonsterFragment" />
app:destination="@id/edit_monster_navigation" />
</fragment>
<fragment
android:id="@+id/editMonsterFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditMonsterFragment"
android:label="Edit Monster"
tools:layout="@layout/fragment_edit_monster">
<navigation
android:id="@+id/edit_monster_navigation"
app:startDestination="@id/editMonsterFragment">
<argument
android:name="monster_id"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/editMonsterFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditMonsterFragment"
android:label="Edit Monster"
tools:layout="@layout/fragment_edit_monster">
<argument
android:name="monster_id"
app:argType="string" />
<action
android:id="@+id/action_editMonsterFragment_to_editBasicInfoFragment"
app:destination="@id/editBasicInfoFragment" />
</fragment>
<fragment
android:id="@+id/editBasicInfoFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditBasicInfoFragment"
android:label="fragment_edit_basic_info"
tools:layout="@layout/fragment_edit_basic_info" />
</navigation>
</navigation>

View File

@@ -2,5 +2,6 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="text_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="text_h4_size">20sp</dimen>
</resources>

View File

@@ -34,4 +34,14 @@
<string name="label_legendary_actions">Legendary Actions</string>
<string name="label_lair_actions">Lair Actions</string>
<string name="label_regional_actions">Regional Actions</string>
<string name="title_edit_monster">Edit %1$s</string>
<string name="default_monster_name">Unnamed Monster</string>
<string name="snackbar_failed_to_create_monster">Failed to create monster</string>
<string name="snackbar_monster_created">%1$s created</string>
<string name="label_name">Name</string>
<string name="label_size">Size</string>
<string name="label_type">Type</string>
<string name="label_subtype">Subtype</string>
<string name="label_alignment">Alignment</string>
<string name="label_custom_hp">Custom HP</string>
</resources>