Merge commit 'd1e3c3f5f313057e5a81a4333906ef5d79adea83' as 'Android'
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
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.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class CollectionsFragment extends MCFragment {
|
||||
|
||||
private CollectionsViewModel collectionsViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
collectionsViewModel = new ViewModelProvider(this).get(CollectionsViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
||||
final TextView textView = root.findViewById(R.id.text_collections);
|
||||
collectionsViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class CollectionsViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<String> mText;
|
||||
|
||||
public CollectionsViewModel() {
|
||||
mText = new MutableLiveData<>();
|
||||
mText.setValue("This is collections fragment");
|
||||
}
|
||||
|
||||
public LiveData<String> getText() {
|
||||
return mText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class AbilityScorePicker extends LinearLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private AbilityScore mSelectedValue;
|
||||
private String mLabel;
|
||||
|
||||
public AbilityScorePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = AbilityScore.STRENGTH;
|
||||
mOnValueChangedListener = null;
|
||||
// TODO: use this as default but allow setting via attribute
|
||||
mLabel = "Ability Score";
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AbilityScorePicker, 0, 0);
|
||||
String label = a.getString(R.styleable.AbilityScorePicker_label);
|
||||
if (label != null) {
|
||||
mLabel = label;
|
||||
}
|
||||
a.recycle();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_ability_score_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.label.setText(mLabel);
|
||||
|
||||
mHolder.spinner.setAdapter(new ArrayAdapter<AbilityScore>(getContext(), R.layout.dropdown_list_item, AbilityScore.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
AbilityScore item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
AbilityScore item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
setValue((AbilityScore) parent.getItemAtPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
setValue(mSelectedValue = AbilityScore.STRENGTH);
|
||||
}
|
||||
});
|
||||
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
|
||||
|
||||
setValue(AbilityScore.STRENGTH);
|
||||
}
|
||||
|
||||
public AbilityScorePicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AbilityScore getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(AbilityScore value) {
|
||||
if (value != mSelectedValue) {
|
||||
mSelectedValue = value;
|
||||
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), value));
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
if (!Objects.equals(mLabel, label)) {
|
||||
mLabel = label;
|
||||
mHolder.label.setText(label);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(AbilityScore value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
private final Spinner spinner;
|
||||
private final TextView label;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
spinner = root.findViewById(R.id.spinner);
|
||||
label = root.findViewById(R.id.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AdvantagePicker extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private AdvantageType mSelectedValue;
|
||||
|
||||
public AdvantagePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = AdvantageType.NONE;
|
||||
mOnValueChangedListener = null;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_advantage_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(AdvantageType.NONE);
|
||||
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (R.id.hasAdvantage == checkedId) {
|
||||
setValue(AdvantageType.ADVANTAGE);
|
||||
} else if (R.id.hasDisadvantage == checkedId) {
|
||||
setValue(AdvantageType.DISADVANTAGE);
|
||||
} else {
|
||||
setValue(AdvantageType.NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AdvantagePicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AdvantageType getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(AdvantageType value) {
|
||||
if (mSelectedValue != value) {
|
||||
mSelectedValue = value;
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(mSelectedValue);
|
||||
}
|
||||
}
|
||||
final int checkedId = mHolder.group.getCheckedRadioButtonId();
|
||||
if (mSelectedValue == AdvantageType.ADVANTAGE) {
|
||||
if (checkedId != R.id.hasAdvantage) {
|
||||
mHolder.advantage.setChecked(true);
|
||||
}
|
||||
} else if (mSelectedValue == AdvantageType.DISADVANTAGE) {
|
||||
if (checkedId != R.id.hasDisadvantage) {
|
||||
mHolder.disadvantage.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
if (checkedId != R.id.none) {
|
||||
mHolder.none.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(AdvantageType value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RadioGroup group;
|
||||
final MaterialRadioButton none;
|
||||
final MaterialRadioButton advantage;
|
||||
final MaterialRadioButton disadvantage;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
group = root.findViewById(R.id.group);
|
||||
none = root.findViewById(R.id.hasNoAdvantage);
|
||||
advantage = root.findViewById(R.id.hasAdvantage);
|
||||
disadvantage = root.findViewById(R.id.hasDisadvantage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ProficiencyPicker extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private ProficiencyType mSelectedValue;
|
||||
|
||||
public ProficiencyPicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = ProficiencyType.NONE;
|
||||
mOnValueChangedListener = null;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_proficiency_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(ProficiencyType.NONE);
|
||||
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (R.id.proficient == checkedId) {
|
||||
setValue(ProficiencyType.PROFICIENT);
|
||||
} else if (R.id.expertise == checkedId) {
|
||||
setValue(ProficiencyType.EXPERTISE);
|
||||
} else {
|
||||
setValue(ProficiencyType.NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ProficiencyPicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ProficiencyType getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(ProficiencyType value) {
|
||||
if (mSelectedValue != value) {
|
||||
mSelectedValue = value;
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(mSelectedValue);
|
||||
}
|
||||
}
|
||||
final int checkedId = mHolder.group.getCheckedRadioButtonId();
|
||||
if (mSelectedValue == ProficiencyType.PROFICIENT) {
|
||||
if (checkedId != R.id.proficient) {
|
||||
mHolder.proficient.setChecked(true);
|
||||
}
|
||||
} else if (mSelectedValue == ProficiencyType.EXPERTISE) {
|
||||
if (checkedId != R.id.expertise) {
|
||||
mHolder.expertise.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
if (checkedId != R.id.none) {
|
||||
mHolder.none.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(ProficiencyType value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RadioGroup group;
|
||||
final MaterialRadioButton none;
|
||||
final MaterialRadioButton proficient;
|
||||
final MaterialRadioButton expertise;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
group = root.findViewById(R.id.group);
|
||||
none = root.findViewById(R.id.none);
|
||||
proficient = root.findViewById(R.id.proficient);
|
||||
expertise = root.findViewById(R.id.expertise);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Stepper extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private int mCurrentValue;
|
||||
private int mStep;
|
||||
private int mMinValue;
|
||||
private int mMaxValue;
|
||||
private String mLabel;
|
||||
private OnValueChangeListener mOnValueChangeListener;
|
||||
private OnFormatValueCallback mOnFormatValueCallback;
|
||||
|
||||
public Stepper(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mCurrentValue = 0;
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Stepper, 0, 0);
|
||||
mStep = a.getInt(R.styleable.Stepper_stepAmount, 1);
|
||||
mMinValue = a.getInt(R.styleable.Stepper_minValue, Integer.MIN_VALUE);
|
||||
mMaxValue = a.getInt(R.styleable.Stepper_maxValue, Integer.MAX_VALUE);
|
||||
mLabel = a.getString(R.styleable.Stepper_label);
|
||||
a.recycle();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_stepper, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(mCurrentValue);
|
||||
updateDisplayedValue();
|
||||
mHolder.increment.setOnClickListener(v -> setValue(mCurrentValue + mStep));
|
||||
mHolder.decrement.setOnClickListener(v -> setValue(mCurrentValue - mStep));
|
||||
|
||||
mHolder.label.setText(mLabel);
|
||||
}
|
||||
|
||||
public Stepper(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public void setLabel(String newLabel) {
|
||||
if (!Objects.equals(mLabel, newLabel)) {
|
||||
mLabel = newLabel;
|
||||
mHolder.label.setText(mLabel);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return mCurrentValue;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
int oldValue = this.mCurrentValue;
|
||||
int newValue = Math.min(mMaxValue, Math.max(mMinValue, value));
|
||||
if (newValue != oldValue) {
|
||||
this.mCurrentValue = newValue;
|
||||
if (mOnValueChangeListener != null) {
|
||||
mOnValueChangeListener.onChange(newValue, oldValue);
|
||||
}
|
||||
updateDisplayedValue();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDisplayedValue() {
|
||||
if (mOnFormatValueCallback != null) {
|
||||
mHolder.text.setText(mOnFormatValueCallback.onFormatValue(this.mCurrentValue));
|
||||
} else {
|
||||
mHolder.text.setText(String.valueOf(this.mCurrentValue));
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangeListener(OnValueChangeListener listener) {
|
||||
mOnValueChangeListener = listener;
|
||||
}
|
||||
|
||||
public void setOnFormatValueCallback(OnFormatValueCallback callback) {
|
||||
mOnFormatValueCallback = callback;
|
||||
updateDisplayedValue();
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return mStep;
|
||||
}
|
||||
|
||||
public void setStep(int step) {
|
||||
this.mStep = step;
|
||||
}
|
||||
|
||||
public int getMinValue() {
|
||||
return mMinValue;
|
||||
}
|
||||
|
||||
public void setMinValue(int minValue) {
|
||||
this.mMinValue = minValue;
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
public void setMaxValue(int maxValue) {
|
||||
this.mMaxValue = maxValue;
|
||||
}
|
||||
|
||||
public interface OnValueChangeListener {
|
||||
void onChange(int value, int previousValue);
|
||||
}
|
||||
|
||||
public interface OnFormatValueCallback {
|
||||
String onFormatValue(int value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final TextView text;
|
||||
final TextView label;
|
||||
final Button increment;
|
||||
final Button decrement;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
text = root.findViewById(R.id.text);
|
||||
label = root.findViewById(R.id.label);
|
||||
increment = root.findViewById(R.id.increment);
|
||||
decrement = root.findViewById(R.id.decrement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class DashboardFragment extends MCFragment {
|
||||
private DashboardViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private DashboardRecyclerViewAdapter mAdapter;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setupRecyclerView(mHolder.list);
|
||||
|
||||
getMonsterRepository()
|
||||
.getMonsters()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(monsters -> mViewModel.setMonsters(monsters));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396);
|
||||
Logger.logWTF(String.format(Locale.US, "Setting column count to %d", columnCount));
|
||||
Context context = requireContext();
|
||||
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<Monster>> monsterData = mViewModel.getMonsters();
|
||||
mAdapter = new DashboardRecyclerViewAdapter(monster -> {
|
||||
if (monster != null) {
|
||||
navigateToMonsterDetail(monster);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
|
||||
}
|
||||
});
|
||||
if (monsterData != null) {
|
||||
monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters));
|
||||
}
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
private void navigateToMonsterDetail(Monster monster) {
|
||||
NavDirections action = DashboardFragmentDirections.actionNavigationDashboardToNavigationMonster(monster.id.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RecyclerView list;
|
||||
|
||||
ViewHolder(View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.databinding.CardMonsterBinding;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, DashboardRecyclerViewAdapter.ViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return oldItem.id.equals(newItem.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
protected DashboardRecyclerViewAdapter(ItemCallback onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(CardMonsterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Logger.logUnimplementedMethod();
|
||||
Monster monster = getItem(position);
|
||||
holder.monster = monster;
|
||||
holder.name.setText(monster.name);
|
||||
holder.meta.setText(monster.getMeta());
|
||||
holder.strengthAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.strengthSavingThrowAdvantage));
|
||||
holder.strengthModifier.setText(Helpers.getModifierString(monster.getStrengthModifier()));
|
||||
holder.strengthName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.STRENGTH));
|
||||
holder.strengthProficiency.setText(Helpers.getProficiencyAbbreviation(monster.strengthSavingThrowProficiency));
|
||||
|
||||
holder.dexterityAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.dexteritySavingThrowAdvantage));
|
||||
holder.dexterityModifier.setText(Helpers.getModifierString(monster.getDexterityModifier()));
|
||||
holder.dexterityName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.DEXTERITY));
|
||||
holder.dexterityProficiency.setText(Helpers.getProficiencyAbbreviation(monster.dexteritySavingThrowProficiency));
|
||||
|
||||
holder.constitutionAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.constitutionSavingThrowAdvantage));
|
||||
holder.constitutionModifier.setText(Helpers.getModifierString(monster.getConstitutionModifier()));
|
||||
holder.constitutionName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.CONSTITUTION));
|
||||
holder.constitutionProficiency.setText(Helpers.getProficiencyAbbreviation(monster.constitutionSavingThrowProficiency));
|
||||
|
||||
holder.intelligenceAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.intelligenceSavingThrowAdvantage));
|
||||
holder.intelligenceModifier.setText(Helpers.getModifierString(monster.getIntelligenceModifier()));
|
||||
holder.intelligenceName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.INTELLIGENCE));
|
||||
holder.intelligenceProficiency.setText(Helpers.getProficiencyAbbreviation(monster.intelligenceSavingThrowProficiency));
|
||||
|
||||
holder.wisdomAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.wisdomSavingThrowAdvantage));
|
||||
holder.wisdomModifier.setText(Helpers.getModifierString(monster.getWisdomModifier()));
|
||||
holder.wisdomName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.WISDOM));
|
||||
holder.wisdomProficiency.setText(Helpers.getProficiencyAbbreviation(monster.wisdomSavingThrowProficiency));
|
||||
|
||||
holder.charismaAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.charismaSavingThrowAdvantage));
|
||||
holder.charismaModifier.setText(Helpers.getModifierString(monster.getCharismaModifier()));
|
||||
holder.charismaName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.CHARISMA));
|
||||
holder.charismaProficiency.setText(Helpers.getProficiencyAbbreviation(monster.charismaSavingThrowProficiency));
|
||||
|
||||
holder.armorClass.setText(String.valueOf(monster.getArmorClassValue()));
|
||||
holder.hitPoints.setText(String.valueOf(monster.getHitPointsValue()));
|
||||
holder.challengeRating.setText(holder.challengeRating.getResources().getString(R.string.label_challenge_rating_with_value, Helpers.getChallengeRatingAbbreviation(monster.challengeRating)));
|
||||
|
||||
int numActions = monster.actions.size();
|
||||
if (numActions > 0) {
|
||||
holder.action1Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(0);
|
||||
holder.action1Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action1Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action1Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (numActions > 1) {
|
||||
holder.action2Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(1);
|
||||
holder.action2Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action2Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action2Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (numActions > 2) {
|
||||
holder.action3Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(2);
|
||||
holder.action3Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action3Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action3Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.monster);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Monster monster);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView name;
|
||||
public final TextView meta;
|
||||
public final View action1Group;
|
||||
public final TextView action1Name;
|
||||
public final TextView action1Description;
|
||||
public final View action2Group;
|
||||
public final TextView action2Name;
|
||||
public final TextView action2Description;
|
||||
public final View action3Group;
|
||||
public final TextView action3Name;
|
||||
public final TextView action3Description;
|
||||
public final TextView strengthName;
|
||||
public final TextView strengthModifier;
|
||||
public final TextView strengthProficiency;
|
||||
public final TextView strengthAdvantage;
|
||||
public final TextView dexterityName;
|
||||
public final TextView dexterityModifier;
|
||||
public final TextView dexterityProficiency;
|
||||
public final TextView dexterityAdvantage;
|
||||
public final TextView constitutionName;
|
||||
public final TextView constitutionModifier;
|
||||
public final TextView constitutionProficiency;
|
||||
public final TextView constitutionAdvantage;
|
||||
public final TextView intelligenceName;
|
||||
public final TextView intelligenceModifier;
|
||||
public final TextView intelligenceProficiency;
|
||||
public final TextView intelligenceAdvantage;
|
||||
public final TextView wisdomName;
|
||||
public final TextView wisdomModifier;
|
||||
public final TextView wisdomProficiency;
|
||||
public final TextView wisdomAdvantage;
|
||||
public final TextView charismaName;
|
||||
public final TextView charismaModifier;
|
||||
public final TextView charismaProficiency;
|
||||
public final TextView charismaAdvantage;
|
||||
public final TextView armorClass;
|
||||
public final TextView hitPoints;
|
||||
public final TextView challengeRating;
|
||||
public Monster monster;
|
||||
|
||||
public ViewHolder(@NonNull CardMonsterBinding binding) {
|
||||
super(binding.getRoot());
|
||||
name = binding.name;
|
||||
meta = binding.meta;
|
||||
action1Group = binding.action1.getRoot();
|
||||
action1Name = binding.action1.name;
|
||||
action1Description = binding.action1.description;
|
||||
action2Group = binding.action2.getRoot();
|
||||
action2Name = binding.action2.name;
|
||||
action2Description = binding.action2.description;
|
||||
action3Group = binding.action3.getRoot();
|
||||
action3Name = binding.action3.name;
|
||||
action3Description = binding.action3.description;
|
||||
strengthName = binding.strength.name;
|
||||
strengthModifier = binding.strength.modifier;
|
||||
strengthProficiency = binding.strength.proficiency;
|
||||
strengthAdvantage = binding.strength.advantage;
|
||||
dexterityName = binding.dexterity.name;
|
||||
dexterityModifier = binding.dexterity.modifier;
|
||||
dexterityProficiency = binding.dexterity.proficiency;
|
||||
dexterityAdvantage = binding.dexterity.advantage;
|
||||
constitutionName = binding.constitution.name;
|
||||
constitutionModifier = binding.constitution.modifier;
|
||||
constitutionProficiency = binding.constitution.proficiency;
|
||||
constitutionAdvantage = binding.constitution.advantage;
|
||||
intelligenceName = binding.intelligence.name;
|
||||
intelligenceModifier = binding.intelligence.modifier;
|
||||
intelligenceProficiency = binding.intelligence.proficiency;
|
||||
intelligenceAdvantage = binding.intelligence.advantage;
|
||||
wisdomName = binding.wisdom.name;
|
||||
wisdomModifier = binding.wisdom.modifier;
|
||||
wisdomProficiency = binding.wisdom.proficiency;
|
||||
wisdomAdvantage = binding.wisdom.advantage;
|
||||
charismaName = binding.charisma.name;
|
||||
charismaModifier = binding.charisma.modifier;
|
||||
charismaProficiency = binding.charisma.proficiency;
|
||||
charismaAdvantage = binding.charisma.advantage;
|
||||
armorClass = binding.armorClass.value;
|
||||
hitPoints = binding.hitPoints.value;
|
||||
challengeRating = binding.challengeRating;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Helpers {
|
||||
@NonNull
|
||||
public static String getModifierString(int value) {
|
||||
return String.format(Locale.getDefault(), "%+d", value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getAbilityScoreAbbreviation(@NonNull AbilityScore abilityScore) {
|
||||
switch (abilityScore) {
|
||||
case STRENGTH:
|
||||
return "S";
|
||||
case DEXTERITY:
|
||||
return "D";
|
||||
case CONSTITUTION:
|
||||
return "C";
|
||||
case INTELLIGENCE:
|
||||
return "I";
|
||||
case WISDOM:
|
||||
return "W";
|
||||
case CHARISMA:
|
||||
return "Ch";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for AbilityScore value %s", abilityScore));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getChallengeRatingAbbreviation(@NonNull ChallengeRating challengeRating) {
|
||||
Logger.logUnimplementedMethod();
|
||||
switch (challengeRating) {
|
||||
case CUSTOM:
|
||||
return "*";
|
||||
case ZERO:
|
||||
return "0";
|
||||
case ONE_EIGHTH:
|
||||
return "1/8";
|
||||
case ONE_QUARTER:
|
||||
return "1/4";
|
||||
case ONE_HALF:
|
||||
return "1/2";
|
||||
case ONE:
|
||||
return "1";
|
||||
case TWO:
|
||||
return "2";
|
||||
case THREE:
|
||||
return "3";
|
||||
case FOUR:
|
||||
return "4";
|
||||
case FIVE:
|
||||
return "5";
|
||||
case SIX:
|
||||
return "6";
|
||||
case SEVEN:
|
||||
return "7";
|
||||
case EIGHT:
|
||||
return "8";
|
||||
case NINE:
|
||||
return "9";
|
||||
case TEN:
|
||||
return "10";
|
||||
case ELEVEN:
|
||||
return "11";
|
||||
case TWELVE:
|
||||
return "12";
|
||||
case THIRTEEN:
|
||||
return "13";
|
||||
case FOURTEEN:
|
||||
return "14";
|
||||
case FIFTEEN:
|
||||
return "15";
|
||||
case SIXTEEN:
|
||||
return "16";
|
||||
case SEVENTEEN:
|
||||
return "17";
|
||||
case EIGHTEEN:
|
||||
return "18";
|
||||
case NINETEEN:
|
||||
return "19";
|
||||
case TWENTY:
|
||||
return "20";
|
||||
case TWENTY_ONE:
|
||||
return "21";
|
||||
case TWENTY_TWO:
|
||||
return "22";
|
||||
case TWENTY_THREE:
|
||||
return "23";
|
||||
case TWENTY_FOUR:
|
||||
return "24";
|
||||
case TWENTY_FIVE:
|
||||
return "25";
|
||||
case TWENTY_SIX:
|
||||
return "26";
|
||||
case TWENTY_SEVEN:
|
||||
return "27";
|
||||
case TWENTY_EIGHT:
|
||||
return "28";
|
||||
case TWENTY_NINE:
|
||||
return "29";
|
||||
case THIRTY:
|
||||
return "30";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for ChallengeRating value %s", challengeRating));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getProficiencyAbbreviation(@NonNull ProficiencyType proficiency) {
|
||||
switch (proficiency) {
|
||||
case NONE:
|
||||
return "";
|
||||
case EXPERTISE:
|
||||
return "E";
|
||||
case PROFICIENT:
|
||||
return "P";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for ProficiencyType value %s", proficiency));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getAdvantageAbbreviation(@NonNull AdvantageType advantage) {
|
||||
switch (advantage) {
|
||||
case NONE:
|
||||
return "";
|
||||
case ADVANTAGE:
|
||||
return "A";
|
||||
case DISADVANTAGE:
|
||||
return "D";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for AdvantageType value %s", advantage));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<Monster>> mMonsters;
|
||||
|
||||
public DashboardViewModel() {
|
||||
mMonsters = new MutableLiveData<>(new ArrayList<>());
|
||||
}
|
||||
|
||||
public LiveData<List<Monster>> getMonsters() {
|
||||
return mMonsters;
|
||||
}
|
||||
|
||||
public void setMonsters(List<Monster> monsters) {
|
||||
mMonsters.setValue(monsters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class EditAbilityScoresFragment extends MCFragment {
|
||||
private final String ABILITY_SCORE_FORMAT = "%d (%+d)";
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private int getModifier(int value) {
|
||||
return value / 2 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_ability_scores, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), value -> mHolder.strength.setValue(value));
|
||||
mHolder.strength.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setStrength(newValue));
|
||||
mHolder.strength.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), value -> mHolder.dexterity.setValue(value));
|
||||
mHolder.dexterity.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setDexterity(newValue));
|
||||
mHolder.dexterity.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), value -> mHolder.constitution.setValue(value));
|
||||
mHolder.constitution.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setConstitution(newValue));
|
||||
mHolder.constitution.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), value -> mHolder.intelligence.setValue(value));
|
||||
mHolder.intelligence.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setIntelligence(newValue));
|
||||
mHolder.intelligence.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), value -> mHolder.wisdom.setValue(value));
|
||||
mHolder.wisdom.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWisdom(newValue));
|
||||
mHolder.wisdom.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), value -> mHolder.charisma.setValue(value));
|
||||
mHolder.charisma.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setCharisma(newValue));
|
||||
mHolder.charisma.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final Stepper strength;
|
||||
final Stepper dexterity;
|
||||
final Stepper constitution;
|
||||
final Stepper intelligence;
|
||||
final Stepper wisdom;
|
||||
final Stepper charisma;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditArmorFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_armor, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.armorType.setAdapter(new ArrayAdapter<ArmorType>(requireContext(), R.layout.dropdown_list_item, ArmorType.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ArmorType item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ArmorType item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.armorType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ArmorType selectedItem = (ArmorType) parent.getItemAtPosition(position);
|
||||
mViewModel.setArmorType(selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mViewModel.setArmorType(ArmorType.NONE);
|
||||
}
|
||||
});
|
||||
mHolder.armorType.setSelection(ArrayHelper.indexOf(ArmorType.values(), mViewModel.getArmorType().getValue()));
|
||||
|
||||
mHolder.naturalArmorBonus.setValue(mViewModel.getNaturalArmorBonusUnboxed());
|
||||
mHolder.naturalArmorBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setNaturalArmorBonus(newValue));
|
||||
|
||||
mHolder.hasShield.setChecked(mViewModel.getHasShieldValueAsBoolean());
|
||||
mHolder.hasShield.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasShield(isChecked));
|
||||
|
||||
mHolder.shieldBonus.setValue(mViewModel.getShieldBonusUnboxed());
|
||||
mHolder.shieldBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setShieldBonus(newValue));
|
||||
|
||||
mHolder.customArmor.setText(mViewModel.getCustomArmor().getValue());
|
||||
mHolder.customArmor.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomArmor(s.toString()))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private final Spinner armorType;
|
||||
private final Stepper naturalArmorBonus;
|
||||
private final SwitchCompat hasShield;
|
||||
private final Stepper shieldBonus;
|
||||
private final EditText customArmor;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
armorType = root.findViewById(R.id.armorType);
|
||||
naturalArmorBonus = root.findViewById(R.id.naturalArmorBonus);
|
||||
hasShield = root.findViewById(R.id.hasShield);
|
||||
shieldBonus = root.findViewById(R.id.shieldBonus);
|
||||
customArmor = root.findViewById(R.id.customArmor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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.utils.TextChangedListener;
|
||||
|
||||
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,
|
||||
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);
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.size.setText(mViewModel.getSize().getValue());
|
||||
mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSize(s.toString())));
|
||||
|
||||
mHolder.type.setText(mViewModel.getType().getValue());
|
||||
mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setType(s.toString())));
|
||||
|
||||
mHolder.subtype.setText(mViewModel.getSubtype().getValue());
|
||||
mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSubtype(s.toString())));
|
||||
|
||||
mHolder.alignment.setText(mViewModel.getAlignment().getValue());
|
||||
mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setAlignment(s.toString())));
|
||||
|
||||
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));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private final EditText name;
|
||||
private final EditText size;
|
||||
private final EditText type;
|
||||
private final EditText subtype;
|
||||
private final EditText alignment;
|
||||
private final EditText customHitPoints;
|
||||
private final Stepper hitDice;
|
||||
private final SwitchMaterial hasCustomHitPoints;
|
||||
|
||||
ViewHolder(@NonNull 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditChallengeRatingFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_challenge_rating, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.challengeRating.setAdapter(new ArrayAdapter<ChallengeRating>(requireContext(), R.layout.dropdown_list_item, ChallengeRating.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ChallengeRating item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ChallengeRating item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.challengeRating.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ChallengeRating selectedItem = (ChallengeRating) parent.getItemAtPosition(position);
|
||||
mViewModel.setChallengeRating(selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mViewModel.setChallengeRating(ChallengeRating.CUSTOM);
|
||||
}
|
||||
});
|
||||
mHolder.challengeRating.setSelection(ArrayHelper.indexOf(ChallengeRating.values(), mViewModel.getChallengeRating().getValue()));
|
||||
|
||||
mHolder.customChallengeRatingDescription.setText(mViewModel.getCustomChallengeRatingDescription().getValue());
|
||||
mHolder.customChallengeRatingDescription.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomChallengeRatingDescription(s.toString()))));
|
||||
|
||||
mHolder.customProficiencyBonus.setText(mViewModel.getCustomProficiencyBonusValueAsString());
|
||||
mHolder.customProficiencyBonus.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomProficiencyBonus(s.toString()))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final Spinner challengeRating;
|
||||
final EditText customChallengeRatingDescription;
|
||||
final EditText customProficiencyBonus;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
challengeRating = root.findViewById(R.id.challengeRating);
|
||||
customChallengeRatingDescription = root.findViewById(R.id.customChallengeRatingDescription);
|
||||
customProficiencyBonus = root.findViewById(R.id.customProficiencyBonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditLanguageFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditLanguageViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private Language mOldLanguage;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditLanguageViewModel.class);
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
EditLanguageFragmentArgs args = EditLanguageFragmentArgs.fromBundle(arguments);
|
||||
mOldLanguage = new Language(args.getName(), args.getCanSpeak());
|
||||
mViewModel.copyFromLanguage(mOldLanguage);
|
||||
} else {
|
||||
Logger.logWTF("EditLanguageFragment needs arguments");
|
||||
mOldLanguage = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_language, 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.canSpeak.setChecked(mViewModel.getCanSpeakValue());
|
||||
mHolder.canSpeak.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setCanSpeak(isChecked));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceLanguage(mOldLanguage, mViewModel.getLanguage().getValue());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText name;
|
||||
SwitchCompat canSpeak;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
canSpeak = root.findViewById(R.id.canSpeak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditLanguageViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<Boolean> mCanSpeak;
|
||||
private final ChangeTrackedLiveData<Language> mLanguage;
|
||||
|
||||
public EditLanguageViewModel() {
|
||||
super();
|
||||
mName = new ChangeTrackedLiveData<>("New Language", this::makeDirty);
|
||||
mCanSpeak = new ChangeTrackedLiveData<>(true, this::makeDirty);
|
||||
mLanguage = new ChangeTrackedLiveData<>(makeLanguage(), this::makeDirty);
|
||||
}
|
||||
|
||||
public void copyFromLanguage(@NonNull Language language) {
|
||||
mName.resetValue(language.getName());
|
||||
mCanSpeak.resetValue(language.getSpeaks());
|
||||
makeClean();
|
||||
}
|
||||
|
||||
public LiveData<Language> getLanguage() {
|
||||
return mLanguage;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName.setValue(name);
|
||||
mLanguage.setValue(makeLanguage());
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getCanSpeak() {
|
||||
return mCanSpeak;
|
||||
}
|
||||
|
||||
public void setCanSpeak(boolean canSpeak) {
|
||||
mCanSpeak.setValue(canSpeak);
|
||||
mLanguage.setValue(makeLanguage());
|
||||
}
|
||||
|
||||
public boolean getCanSpeakValue(boolean defaultIfNull) {
|
||||
Boolean boxedValue = mCanSpeak.getValue();
|
||||
if (boxedValue == null) {
|
||||
return defaultIfNull;
|
||||
}
|
||||
return boxedValue;
|
||||
}
|
||||
|
||||
public boolean getCanSpeakValue() {
|
||||
return getCanSpeakValue(false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Language makeLanguage() {
|
||||
Boolean boxedValue = mCanSpeak.getValue();
|
||||
boolean canSpeak = boxedValue != null && boxedValue;
|
||||
return new Language(mName.getValue(), canSpeak);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditLanguagesFragment extends MCFragment {
|
||||
// TODO: Make the swipe to delete not happen for the header
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private void navigateToEditLanguage(@NonNull Language language) {
|
||||
NavDirections action = EditLanguagesFragmentDirections.actionEditLanguagesFragmentToEditLanguageFragment(language.getName(), language.getSpeaks());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_languages_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddLanguageButton(mHolder.addLanguage);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> {
|
||||
EditLanguagesRecyclerViewAdapter adapter = new EditLanguagesRecyclerViewAdapter(
|
||||
mViewModel.getLanguagesArray(),
|
||||
language -> {
|
||||
if (language != null) {
|
||||
navigateToEditLanguage(language);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditSkill with a null skill");
|
||||
}
|
||||
},
|
||||
mViewModel.getTelepathyRangeUnboxed(),
|
||||
(value, previousValue) -> mViewModel.setTelepathyRange(value),
|
||||
mViewModel.getUnderstandsButDescription().getValue(),
|
||||
new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setUnderstandsButDescription(s.toString())));
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> {
|
||||
if (position > 0) {
|
||||
mViewModel.removeLanguage(position - 1);
|
||||
}
|
||||
}, null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddLanguageButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Language newLanguage = mViewModel.addNewLanguage();
|
||||
navigateToEditLanguage(newLanguage);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addLanguage;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
this.list = root.findViewById(R.id.list);
|
||||
this.addLanguage = root.findViewById(R.id.add_language);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListHeaderBinding;
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private final List<Language> mValues;
|
||||
private final ItemCallback mOnClick;
|
||||
private final int mTelepathyRange;
|
||||
private final String mUnderstandsBut;
|
||||
private final Stepper.OnValueChangeListener mOnTelepathyRangeChanged;
|
||||
private final TextWatcher mOnUnderstandsButChanged;
|
||||
|
||||
private final int HEADER_VIEW_TYPE = 1;
|
||||
private final int ITEM_VIEW_TYPE = 2;
|
||||
private final String DISTANCE_IN_FEET_FORMAT = "%d ft.";
|
||||
|
||||
public EditLanguagesRecyclerViewAdapter(List<Language> items, ItemCallback onClick, int telepathyRange, Stepper.OnValueChangeListener telepathyRangeChangedListener, String understandsBut, TextWatcher understandsButChangedListener) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
mTelepathyRange = telepathyRange;
|
||||
mOnTelepathyRangeChanged = telepathyRangeChangedListener;
|
||||
mUnderstandsBut = understandsBut;
|
||||
mOnUnderstandsButChanged = understandsButChangedListener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == HEADER_VIEW_TYPE) {
|
||||
return new HeaderViewHolder(FragmentEditLanguagesListHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
return new ItemViewHolder(FragmentEditLanguagesListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
|
||||
if (holder instanceof HeaderViewHolder) {
|
||||
HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder;
|
||||
headerViewHolder.telepathy.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), DISTANCE_IN_FEET_FORMAT, value));
|
||||
headerViewHolder.telepathy.setValue(mTelepathyRange);
|
||||
headerViewHolder.telepathy.setOnValueChangeListener(mOnTelepathyRangeChanged);
|
||||
headerViewHolder.understandsBut.setText(mUnderstandsBut);
|
||||
headerViewHolder.understandsBut.addTextChangedListener(mOnUnderstandsButChanged);
|
||||
} else if (holder instanceof ItemViewHolder) {
|
||||
ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
|
||||
itemViewHolder.mItem = mValues.get(position - 1);
|
||||
itemViewHolder.mContentView.setText(itemViewHolder.mItem.getName());
|
||||
itemViewHolder.itemView.setOnClickListener(view -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(itemViewHolder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return HEADER_VIEW_TYPE;
|
||||
}
|
||||
return ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Language language);
|
||||
}
|
||||
|
||||
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
public final Stepper telepathy;
|
||||
public final EditText understandsBut;
|
||||
|
||||
public HeaderViewHolder(@NonNull FragmentEditLanguagesListHeaderBinding binding) {
|
||||
super(binding.getRoot());
|
||||
telepathy = binding.telepathy;
|
||||
understandsBut = binding.understandsBut;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Language mItem;
|
||||
|
||||
public ItemViewHolder(@NonNull FragmentEditLanguagesListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
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.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentArgs;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
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;
|
||||
|
||||
public class EditMonsterFragment extends MCFragment {
|
||||
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
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);
|
||||
|
||||
setTitle(getString(R.string.title_editMonster_fmt, getString(R.string.default_monster_name)));
|
||||
|
||||
// TODO: Show a loading spinner until we have the monster loaded.
|
||||
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) {
|
||||
mViewModel.setHasLoaded(true);
|
||||
mViewModel.setHasError(false);
|
||||
mViewModel.copyFromMonster(monster);
|
||||
setTitle(getString(R.string.title_editMonster_fmt, 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 -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditBasicInfoFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.armorButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditArmorFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.speedButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSpeedFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.abilityScoresButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditAbilityScoresFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.savingThrows.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSavingThrowsFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.challengeRating.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditChallengeRatingFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.skills.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSkillsFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.senses.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.SENSE);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.conditionImmunities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.CONDITION_IMMUNITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageImmunities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_IMMUNITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageResistances.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_RESISTANCE);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageVulnerabilities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_VULNERABILITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.languages.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditLanguagesFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.abilities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.ABILITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.actions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.lairActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.LAIR_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.legendaryActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.LEGENDARY_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.reactions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.REACTIONS);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.regionalActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.REGIONAL_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
|
||||
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 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 {
|
||||
|
||||
TextView basicInfoButton;
|
||||
TextView armorButton;
|
||||
TextView speedButton;
|
||||
TextView abilityScoresButton;
|
||||
TextView savingThrows;
|
||||
TextView skills;
|
||||
TextView conditionImmunities;
|
||||
TextView damageImmunities;
|
||||
TextView damageResistances;
|
||||
TextView damageVulnerabilities;
|
||||
TextView senses;
|
||||
TextView languages;
|
||||
TextView challengeRating;
|
||||
TextView abilities;
|
||||
TextView actions;
|
||||
TextView reactions;
|
||||
TextView legendaryActions;
|
||||
TextView lairActions;
|
||||
TextView regionalActions;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
basicInfoButton = root.findViewById(R.id.basicInfo);
|
||||
armorButton = root.findViewById(R.id.armor);
|
||||
speedButton = root.findViewById(R.id.speed);
|
||||
abilityScoresButton = root.findViewById(R.id.abilityScores);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challengeRating = root.findViewById(R.id.challengeRating);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
regionalActions = root.findViewById(R.id.regionalActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class EditSavingThrowsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mViewHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_saving_throws, container, false);
|
||||
mViewHolder = new ViewHolder(root);
|
||||
|
||||
mViewHolder.strengthProficiency.setValue(mViewModel.getStrengthProficiency().getValue());
|
||||
mViewHolder.strengthProficiency.setOnValueChangedListener(value -> mViewModel.setStrengthProficiency(value));
|
||||
mViewHolder.strengthAdvantage.setValue(mViewModel.getStrengthAdvantage().getValue());
|
||||
mViewHolder.strengthAdvantage.setOnValueChangedListener(value -> mViewModel.setStrengthAdvantage(value));
|
||||
|
||||
mViewHolder.dexterityProficiency.setValue(mViewModel.getDexterityProficiency().getValue());
|
||||
mViewHolder.dexterityProficiency.setOnValueChangedListener(value -> mViewModel.setDexterityProficiency(value));
|
||||
mViewHolder.dexterityAdvantage.setValue(mViewModel.getDexterityAdvantage().getValue());
|
||||
mViewHolder.dexterityAdvantage.setOnValueChangedListener(value -> mViewModel.setDexterityAdvantage(value));
|
||||
|
||||
mViewHolder.constitutionProficiency.setValue(mViewModel.getConstitutionProficiency().getValue());
|
||||
mViewHolder.constitutionProficiency.setOnValueChangedListener(value -> mViewModel.setConstitutionProficiency(value));
|
||||
mViewHolder.constitutionAdvantage.setValue(mViewModel.getConstitutionAdvantage().getValue());
|
||||
mViewHolder.constitutionAdvantage.setOnValueChangedListener(value -> mViewModel.setConstitutionAdvantage(value));
|
||||
|
||||
mViewHolder.intelligenceProficiency.setValue(mViewModel.getIntelligenceProficiency().getValue());
|
||||
mViewHolder.intelligenceProficiency.setOnValueChangedListener(value -> mViewModel.setIntelligenceProficiency(value));
|
||||
mViewHolder.intelligenceAdvantage.setValue(mViewModel.getIntelligenceAdvantage().getValue());
|
||||
mViewHolder.intelligenceAdvantage.setOnValueChangedListener(value -> mViewModel.setIntelligenceAdvantage(value));
|
||||
|
||||
mViewHolder.wisdomProficiency.setValue(mViewModel.getWisdomProficiency().getValue());
|
||||
mViewHolder.wisdomProficiency.setOnValueChangedListener(value -> mViewModel.setWisdomProficiency(value));
|
||||
mViewHolder.wisdomAdvantage.setValue(mViewModel.getWisdomAdvantage().getValue());
|
||||
mViewHolder.wisdomAdvantage.setOnValueChangedListener(value -> mViewModel.setWisdomAdvantage(value));
|
||||
|
||||
mViewHolder.charismaProficiency.setValue(mViewModel.getCharismaProficiency().getValue());
|
||||
mViewHolder.charismaProficiency.setOnValueChangedListener(value -> mViewModel.setCharismaProficiency(value));
|
||||
mViewHolder.charismaAdvantage.setValue(mViewModel.getCharismaAdvantage().getValue());
|
||||
mViewHolder.charismaAdvantage.setOnValueChangedListener(value -> mViewModel.setCharismaAdvantage(value));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
AdvantagePicker strengthAdvantage;
|
||||
ProficiencyPicker strengthProficiency;
|
||||
AdvantagePicker dexterityAdvantage;
|
||||
ProficiencyPicker dexterityProficiency;
|
||||
AdvantagePicker constitutionAdvantage;
|
||||
ProficiencyPicker constitutionProficiency;
|
||||
AdvantagePicker intelligenceAdvantage;
|
||||
ProficiencyPicker intelligenceProficiency;
|
||||
AdvantagePicker wisdomAdvantage;
|
||||
ProficiencyPicker wisdomProficiency;
|
||||
AdvantagePicker charismaAdvantage;
|
||||
ProficiencyPicker charismaProficiency;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
strengthAdvantage = root.findViewById(R.id.strengthAdvantage);
|
||||
strengthProficiency = root.findViewById(R.id.strengthProficiency);
|
||||
dexterityAdvantage = root.findViewById(R.id.dexterityAdvantage);
|
||||
dexterityProficiency = root.findViewById(R.id.dexterityProficiency);
|
||||
constitutionAdvantage = root.findViewById(R.id.constitutionAdvantage);
|
||||
constitutionProficiency = root.findViewById(R.id.constitutionProficiency);
|
||||
intelligenceAdvantage = root.findViewById(R.id.intelligenceAdvantage);
|
||||
intelligenceProficiency = root.findViewById(R.id.intelligenceProficiency);
|
||||
wisdomAdvantage = root.findViewById(R.id.wisdomAdvantage);
|
||||
wisdomProficiency = root.findViewById(R.id.wisdomProficiency);
|
||||
charismaAdvantage = root.findViewById(R.id.charismaAdvantage);
|
||||
charismaProficiency = root.findViewById(R.id.charismaProficiency);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.components.AbilityScorePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditSkillFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditSkillViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private Skill mOldSkill;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditSkillViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditSkillFragmentArgs args = EditSkillFragmentArgs.fromBundle(getArguments());
|
||||
mOldSkill = new Skill(args.getName(), args.getAbilityScore(), args.getAdvantage(), args.getProficiency());
|
||||
mViewModel.copyFromSkill(mOldSkill);
|
||||
} else {
|
||||
Logger.logWTF("EditSkillFragment needs arguments.");
|
||||
mOldSkill = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_skill, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.abilityScore.setValue(mViewModel.getAbilityScore().getValue());
|
||||
mHolder.abilityScore.setOnValueChangedListener(value -> mViewModel.setAbilityScore(value));
|
||||
|
||||
mHolder.advantage.setValue(mViewModel.getAdvantage().getValue());
|
||||
mHolder.advantage.setOnValueChangedListener(value -> mViewModel.setAdvantage(value));
|
||||
|
||||
mHolder.proficiency.setValue(mViewModel.getProficiency().getValue());
|
||||
mHolder.proficiency.setOnValueChangedListener(value -> mViewModel.setProficiency(value));
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceSkill(mViewModel.getSkill().getValue(), mOldSkill);
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
AbilityScorePicker abilityScore;
|
||||
AdvantagePicker advantage;
|
||||
ProficiencyPicker proficiency;
|
||||
EditText name;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
abilityScore = root.findViewById(R.id.abilityScore);
|
||||
advantage = root.findViewById(R.id.advantage);
|
||||
proficiency = root.findViewById(R.id.proficiency);
|
||||
name = root.findViewById(R.id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditSkillViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<AbilityScore> mAbilityScore;
|
||||
private final ChangeTrackedLiveData<AdvantageType> mAdvantageType;
|
||||
private final ChangeTrackedLiveData<ProficiencyType> mProficiencyType;
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<Skill> mSkill;
|
||||
|
||||
public EditSkillViewModel() {
|
||||
super();
|
||||
mAbilityScore = new ChangeTrackedLiveData<>(AbilityScore.STRENGTH, this::makeDirty);
|
||||
mAdvantageType = new ChangeTrackedLiveData<>(AdvantageType.NONE, this::makeDirty);
|
||||
mProficiencyType = new ChangeTrackedLiveData<>(ProficiencyType.NONE, this::makeDirty);
|
||||
mName = new ChangeTrackedLiveData<>("New Skill", this::makeDirty);
|
||||
mSkill = new ChangeTrackedLiveData<>(makeSkill(), this::makeDirty);
|
||||
}
|
||||
|
||||
public void copyFromSkill(@NonNull Skill skill) {
|
||||
mAbilityScore.resetValue(skill.abilityScore);
|
||||
mAdvantageType.resetValue(skill.advantageType);
|
||||
mProficiencyType.resetValue(skill.proficiencyType);
|
||||
mName.resetValue(skill.name);
|
||||
makeClean();
|
||||
}
|
||||
|
||||
public LiveData<Skill> getSkill() {
|
||||
return mSkill;
|
||||
}
|
||||
|
||||
public LiveData<AbilityScore> getAbilityScore() {
|
||||
return mAbilityScore;
|
||||
}
|
||||
|
||||
public void setAbilityScore(AbilityScore value) {
|
||||
mAbilityScore.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<AdvantageType> getAdvantage() {
|
||||
return mAdvantageType;
|
||||
}
|
||||
|
||||
public void setAdvantage(AdvantageType value) {
|
||||
mAdvantageType.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<ProficiencyType> getProficiency() {
|
||||
return mProficiencyType;
|
||||
}
|
||||
|
||||
public void setProficiency(ProficiencyType value) {
|
||||
mProficiencyType.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String value) {
|
||||
mName.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Skill makeSkill() {
|
||||
return new Skill(mName.getValue(), mAbilityScore.getValue(), mAdvantageType.getValue(), mProficiencyType.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Items.
|
||||
*/
|
||||
public class EditSkillsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private void navigateToEditSkill(@NonNull Skill skill) {
|
||||
NavDirections action = EditSkillsFragmentDirections.actionEditSkillsFragmentToEditSkillFragment(skill.name, skill.abilityScore, skill.proficiencyType, skill.advantageType);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_skills_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddSkillButton(mHolder.addSkill);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> {
|
||||
EditSkillsRecyclerViewAdapter adapter = new EditSkillsRecyclerViewAdapter(mViewModel.getSkillsArray(), skill -> {
|
||||
if (skill != null) {
|
||||
navigateToEditSkill(skill);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditSkill with a null skill");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeSkill(position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddSkillButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Skill newSkill = mViewModel.addNewSkill();
|
||||
navigateToEditSkill(newSkill);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addSkill;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
this.list = root.findViewById(R.id.list);
|
||||
this.addSkill = root.findViewById(R.id.add_skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditSkillsListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link RecyclerView.Adapter} that can display a {@link Skill}.
|
||||
*/
|
||||
public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkillsRecyclerViewAdapter.ViewHolder> {
|
||||
private final List<Skill> mValues;
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
public EditSkillsRecyclerViewAdapter(List<Skill> items, ItemCallback onClick) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(FragmentEditSkillsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mContentView.setText(mValues.get(position).name);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Skill skill);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Skill mItem;
|
||||
|
||||
public ViewHolder(@NonNull FragmentEditSkillsListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditSpeedFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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_speed, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.baseSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWalkSpeed(newValue));
|
||||
mHolder.baseSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getWalkSpeed().observe(getViewLifecycleOwner(), value -> mHolder.baseSpeed.setValue(value));
|
||||
|
||||
mHolder.burrowSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setBurrowSpeed(newValue));
|
||||
mHolder.burrowSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getBurrowSpeed().observe(getViewLifecycleOwner(), value -> mHolder.burrowSpeed.setValue(value));
|
||||
|
||||
mHolder.climbSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setClimbSpeed(newValue));
|
||||
mHolder.climbSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getClimbSpeed().observe(getViewLifecycleOwner(), value -> mHolder.climbSpeed.setValue(value));
|
||||
|
||||
mHolder.flySpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setFlySpeed(newValue));
|
||||
mHolder.flySpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getFlySpeed().observe(getViewLifecycleOwner(), value -> mHolder.flySpeed.setValue(value));
|
||||
|
||||
mHolder.swimSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setSwimSpeed(newValue));
|
||||
mHolder.swimSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getSwimSpeed().observe(getViewLifecycleOwner(), value -> mHolder.swimSpeed.setValue(value));
|
||||
|
||||
mViewModel.getCanHover().observe(getViewLifecycleOwner(), value -> mHolder.canHover.setChecked(value));
|
||||
mHolder.canHover.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setCanHover(isChecked));
|
||||
|
||||
mViewModel.getHasCustomSpeed().observe(getViewLifecycleOwner(), value -> mHolder.hasCustomSpeed.setChecked(value));
|
||||
mHolder.hasCustomSpeed.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasCustomSpeed(isChecked));
|
||||
|
||||
//mViewModel.getCustomSpeed().observe(getViewLifecycleOwner(), value -> mHolder.customSpeed.setText(value));
|
||||
mHolder.customSpeed.setText(mViewModel.getCustomSpeed().getValue());
|
||||
mHolder.customSpeed.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomSpeed(s.toString())));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
final Stepper baseSpeed;
|
||||
final Stepper burrowSpeed;
|
||||
final Stepper climbSpeed;
|
||||
final Stepper flySpeed;
|
||||
final Stepper swimSpeed;
|
||||
final SwitchCompat canHover;
|
||||
final SwitchCompat hasCustomSpeed;
|
||||
final EditText customSpeed;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
baseSpeed = root.findViewById(R.id.baseSpeed);
|
||||
burrowSpeed = root.findViewById(R.id.burrowSpeed);
|
||||
climbSpeed = root.findViewById(R.id.climbSpeed);
|
||||
flySpeed = root.findViewById(R.id.flySpeed);
|
||||
swimSpeed = root.findViewById(R.id.swimSpeed);
|
||||
canHover = root.findViewById(R.id.canHover);
|
||||
hasCustomSpeed = root.findViewById(R.id.hasCustomSpeed);
|
||||
customSpeed = root.findViewById(R.id.customSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditStringFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditStringViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private String mOldValue;
|
||||
private StringType mStringType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditStringViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditStringFragmentArgs args = EditStringFragmentArgs.fromBundle(getArguments());
|
||||
mOldValue = args.getValue();
|
||||
mViewModel.setValue(mOldValue);
|
||||
mStringType = args.getStringType();
|
||||
} else {
|
||||
Logger.logWTF("EditStringFragment needs arguments");
|
||||
mOldValue = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_string, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForStringType(mStringType));
|
||||
|
||||
mHolder.description.setText(mViewModel.getValueAsString());
|
||||
mHolder.description.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setValue(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceString(mStringType, mOldValue, mViewModel.getValueAsString());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForStringType(@NonNull StringType type) {
|
||||
switch (type) {
|
||||
case CONDITION_IMMUNITY:
|
||||
return getString(R.string.title_editConditionImmunity);
|
||||
case DAMAGE_IMMUNITY:
|
||||
return getString(R.string.title_editDamageImmunity);
|
||||
case DAMAGE_RESISTANCE:
|
||||
return getString(R.string.title_editDamageResistance);
|
||||
case DAMAGE_VULNERABILITY:
|
||||
return getString(R.string.title_editDamageVulnerability);
|
||||
case SENSE:
|
||||
return getString(R.string.title_editSense);
|
||||
default:
|
||||
return getString(R.string.title_editString);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.description.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText description;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
description = root.findViewById(R.id.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditStringViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mValue;
|
||||
|
||||
public EditStringViewModel() {
|
||||
super();
|
||||
mValue = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
}
|
||||
|
||||
public LiveData<String> getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
mValue.setValue(value);
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
return mValue.getValue();
|
||||
}
|
||||
|
||||
public void resetValue(String value) {
|
||||
makeClean();
|
||||
mValue.resetValue(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditStringsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private StringType mStringType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
EditStringsFragmentArgs args = EditStringsFragmentArgs.fromBundle(arguments);
|
||||
mStringType = args.getStringType();
|
||||
} else {
|
||||
Logger.logWTF("EditStringsFragment needs arguments");
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable 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_strings_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForStringType(mStringType));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddButton(mHolder.addItem);
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForStringType(StringType type) {
|
||||
switch (type) {
|
||||
case CONDITION_IMMUNITY:
|
||||
return getString(R.string.title_editConditionImmunities);
|
||||
case DAMAGE_IMMUNITY:
|
||||
return getString(R.string.title_editDamageImmunities);
|
||||
case DAMAGE_RESISTANCE:
|
||||
return getString(R.string.title_editDamageResistances);
|
||||
case DAMAGE_VULNERABILITY:
|
||||
return getString(R.string.title_editDamageVulnerabilities);
|
||||
case SENSE:
|
||||
return getString(R.string.title_editSenses);
|
||||
default:
|
||||
return getString(R.string.title_editStrings);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<String>> stringsData = mViewModel.getStrings(mStringType);
|
||||
if (stringsData != null) {
|
||||
stringsData.observe(getViewLifecycleOwner(), strings -> {
|
||||
EditStringsRecyclerViewAdapter adapter = new EditStringsRecyclerViewAdapter(strings, value -> {
|
||||
if (value != null) {
|
||||
navigateToEditString(value);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditStringFragment with a null trait");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
}
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeString(mStringType, position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
String newValue = mViewModel.addNewString(mStringType);
|
||||
if (newValue != null) {
|
||||
navigateToEditString(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditString(String value) {
|
||||
NavDirections action = EditStringsFragmentDirections.actionEditStringsFragmentToEditStringFragment(mStringType, value);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addItem;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
addItem = root.findViewById(R.id.add_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditStringsListItemBinding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStringsRecyclerViewAdapter.ViewHolder> {
|
||||
private final List<String> mValues;
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
public EditStringsRecyclerViewAdapter(List<String> items, ItemCallback onClick) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(FragmentEditStringsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mContentView.setText(mValues.get(position));
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(String value);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public String mItem;
|
||||
|
||||
public ViewHolder(@NonNull FragmentEditStringsListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditTraitFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditTraitViewModel mViewModel;
|
||||
private EditTraitFragment.ViewHolder mHolder;
|
||||
private Trait mOldValue;
|
||||
private TraitType mTraitType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditTraitViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditTraitFragmentArgs args = EditTraitFragmentArgs.fromBundle(getArguments());
|
||||
mOldValue = new Trait(args.getName(), args.getDescription());
|
||||
mViewModel.copyFromTrait(mOldValue);
|
||||
mTraitType = args.getTraitType();
|
||||
} else {
|
||||
Logger.logWTF("EditTraitFragment needs arguments");
|
||||
mOldValue = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_trait, container, false);
|
||||
mHolder = new EditTraitFragment.ViewHolder(root);
|
||||
setTitle(getTitleForTraitType(mTraitType));
|
||||
|
||||
mHolder.name.setText(mViewModel.getNameAsString());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.description.setText(mViewModel.getDescriptionAsString());
|
||||
mHolder.description.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setDescription(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceTrait(mTraitType, mOldValue, mViewModel.getAbilityValue());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private String getTitleForTraitType(TraitType type) {
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
return getString(R.string.title_editAbility);
|
||||
case ACTION:
|
||||
return getString(R.string.title_editAction);
|
||||
case LAIR_ACTION:
|
||||
return getString(R.string.title_editLairAction);
|
||||
case LEGENDARY_ACTION:
|
||||
return getString(R.string.title_editLegendaryAction);
|
||||
case REACTIONS:
|
||||
return getString(R.string.title_editReaction);
|
||||
case REGIONAL_ACTION:
|
||||
return getString(R.string.title_editRegionalAction);
|
||||
default:
|
||||
return getString(R.string.title_editTrait);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText description;
|
||||
EditText name;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
description = root.findViewById(R.id.description);
|
||||
name = root.findViewById(R.id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditTraitViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<String> mDescription;
|
||||
private final MutableLiveData<Trait> mAbility;
|
||||
|
||||
public EditTraitViewModel() {
|
||||
super();
|
||||
mName = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
mDescription = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
mAbility = new MutableLiveData<>(makeAbility());
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName.setValue(name);
|
||||
mAbility.setValue(makeAbility());
|
||||
}
|
||||
|
||||
public String getNameAsString() {
|
||||
return mName.getValue();
|
||||
}
|
||||
|
||||
public LiveData<String> getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
mDescription.setValue(description);
|
||||
mAbility.setValue(makeAbility());
|
||||
}
|
||||
|
||||
public String getDescriptionAsString() {
|
||||
return mDescription.getValue();
|
||||
}
|
||||
|
||||
public Trait getAbilityValue() {
|
||||
return mAbility.getValue();
|
||||
}
|
||||
|
||||
public void copyFromTrait(@NonNull Trait trait) {
|
||||
makeClean();
|
||||
mName.resetValue(trait.name);
|
||||
mDescription.resetValue(trait.description);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Trait makeAbility() {
|
||||
return new Trait(mName.getValue(), mDescription.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditTraitsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private TraitType mTraitType;
|
||||
private EditTraitsRecyclerViewAdapter mAdapter;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
if (getArguments() != null) {
|
||||
EditTraitsFragmentArgs args = EditTraitsFragmentArgs.fromBundle(getArguments());
|
||||
mTraitType = args.getTraitType();
|
||||
} else {
|
||||
Logger.logWTF("EditTraitFragment needs arguments");
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable 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_traits_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForTraitType(mTraitType));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddButton(mHolder.addTrait);
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForTraitType(TraitType type) {
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
return getString(R.string.title_editAbilities);
|
||||
case ACTION:
|
||||
return getString(R.string.title_editActions);
|
||||
case LAIR_ACTION:
|
||||
return getString(R.string.title_editLairActions);
|
||||
case LEGENDARY_ACTION:
|
||||
return getString(R.string.title_editLegendaryActions);
|
||||
case REACTIONS:
|
||||
return getString(R.string.title_editReactions);
|
||||
case REGIONAL_ACTION:
|
||||
return getString(R.string.title_editRegionalActions);
|
||||
default:
|
||||
return getString(R.string.title_editTraits);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<Trait>> traitData = mViewModel.getTraits(mTraitType);
|
||||
mAdapter = new EditTraitsRecyclerViewAdapter(trait -> {
|
||||
if (trait != null) {
|
||||
navigateToEditTrait(trait);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditTraitFragment with a null trait");
|
||||
}
|
||||
});
|
||||
if (traitData != null) {
|
||||
traitData.observe(getViewLifecycleOwner(), traits -> mAdapter.submitList(traits));
|
||||
}
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeTrait(mTraitType, position), (from, to) -> mViewModel.moveTrait(mTraitType, from, to)));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Trait newTrait = mViewModel.addNewTrait(mTraitType);
|
||||
if (newTrait != null) {
|
||||
navigateToEditTrait(newTrait);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditTrait(@NonNull Trait trait) {
|
||||
NavDirections action = EditTraitsFragmentDirections.actionEditTraitListFragmentToEditTraitFragment(trait.description, trait.name, mTraitType);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addTrait;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
addTrait = root.findViewById(R.id.add_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditTraitsListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
|
||||
public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraitsRecyclerViewAdapter.ViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Trait> DIFF_CALLBACK = new DiffUtil.ItemCallback<Trait>() {
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Trait oldItem, @NonNull Trait newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Trait oldItem, @NonNull Trait newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
protected EditTraitsRecyclerViewAdapter(ItemCallback onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(FragmentEditTraitsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = getItem(position);
|
||||
holder.mContentView.setText(holder.mItem.name);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Trait trait);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Trait mItem;
|
||||
|
||||
public ViewHolder(@NonNull FragmentEditTraitsListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.majinnaibu.monstercards.ui.library;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class LibraryFragment extends MCFragment {
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_library, container, false);
|
||||
|
||||
FloatingActionButton fab = root.findViewById(R.id.fab);
|
||||
assert fab != null;
|
||||
setupAddMonsterButton(fab);
|
||||
|
||||
final RecyclerView recyclerView = root.findViewById(R.id.monster_list);
|
||||
assert recyclerView != null;
|
||||
setupRecyclerView(recyclerView);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
|
||||
LibraryRecyclerViewAdapter adapter = new LibraryRecyclerViewAdapter(
|
||||
context,
|
||||
repository.getMonsters(),
|
||||
(monster) -> navigateToMonsterDetail(monster.id),
|
||||
(monster) -> repository
|
||||
.deleteMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
}
|
||||
}));
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(requireContext(), (position, direction) -> adapter.deleteItem(position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddMonsterButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Monster monster = new Monster();
|
||||
monster.name = getString(R.string.default_monster_name);
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
repository.addMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Snackbar.make(
|
||||
view,
|
||||
getString(R.string.snackbar_monster_created, monster.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", (_view) -> navigateToMonsterDetail(monster.id))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToMonsterDetail(@NonNull UUID monsterId) {
|
||||
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.majinnaibu.monstercards.ui.library;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class LibraryRecyclerViewAdapter extends RecyclerView.Adapter<LibraryRecyclerViewAdapter.ViewHolder> {
|
||||
private final Context mContext;
|
||||
private final ItemCallback mOnDelete;
|
||||
private final ItemCallback mOnClick;
|
||||
private final Flowable<List<Monster>> mItemsObservable;
|
||||
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(@NonNull View view) {
|
||||
Monster monster = (Monster) view.getTag();
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(monster);
|
||||
}
|
||||
}
|
||||
};
|
||||
private List<Monster> mValues;
|
||||
private Disposable mDisposable;
|
||||
|
||||
public LibraryRecyclerViewAdapter(Context context,
|
||||
Flowable<List<Monster>> itemsObservable,
|
||||
ItemCallback onClick,
|
||||
ItemCallback onDelete) {
|
||||
mItemsObservable = itemsObservable;
|
||||
mValues = new ArrayList<>();
|
||||
mContext = context;
|
||||
mOnDelete = onDelete;
|
||||
mOnClick = onClick;
|
||||
mDisposable = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.monster_list_content, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final @NonNull ViewHolder holder, int position) {
|
||||
Monster monster = mValues.get(position);
|
||||
holder.mContentView.setText(monster.name);
|
||||
holder.itemView.setTag(monster);
|
||||
holder.itemView.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
// TODO: consider moving this subscription out of the adapter and make the subscriber call setItems on the adapter
|
||||
mDisposable = mItemsObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(monsters -> {
|
||||
mValues = monsters;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onDetachedFromRecyclerView(recyclerView);
|
||||
mDisposable.dispose();
|
||||
}
|
||||
|
||||
public void deleteItem(int position) {
|
||||
if (mOnDelete != null) {
|
||||
Monster monster = mValues.get(position);
|
||||
mOnDelete.onItemCallback(monster);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Monster monster);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView mContentView;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
mContentView = view.findViewById(R.id.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
|
||||
|
||||
public class MonsterDetailFragment extends MCFragment {
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private MonsterDetailViewModel mViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
|
||||
setHasOptionsMenu(true);
|
||||
mViewModel = new ViewModelProvider(this).get(MonsterDetailViewModel.class);
|
||||
repository.getMonster(monsterId).toObservable()
|
||||
.firstOrError()
|
||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||
mViewModel.setMonster(monster);
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
View root = inflater.inflate(R.layout.fragment_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getName().observe(getViewLifecycleOwner(), name -> {
|
||||
mHolder.name.setText(name);
|
||||
setTitle(getString(R.string.title_monsterDetails_fmt, name));
|
||||
});
|
||||
mViewModel.getMeta().observe(getViewLifecycleOwner(), mHolder.meta::setText);
|
||||
mViewModel.getArmorClass().observe(getViewLifecycleOwner(), armorText -> setupLabeledTextView(mHolder.armorClass, armorText, R.string.label_armor_class));
|
||||
mViewModel.getHitPoints().observe(getViewLifecycleOwner(), hitPoints -> setupLabeledTextView(mHolder.hitPoints, hitPoints, R.string.label_hit_points));
|
||||
mViewModel.getSpeed().observe(getViewLifecycleOwner(), speed -> setupLabeledTextView(mHolder.speed, speed, R.string.label_speed));
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), mHolder.strength::setText);
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), mHolder.dexterity::setText);
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), mHolder.constitution::setText);
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), mHolder.intelligence::setText);
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), mHolder.wisdom::setText);
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), mHolder.charisma::setText);
|
||||
mViewModel.getSavingThrows().observe(getViewLifecycleOwner(), savingThrows -> setupOptionalTextView(mHolder.savingThrows, savingThrows, R.string.label_saving_throws));
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> setupOptionalTextView(mHolder.skills, skills, R.string.label_skills));
|
||||
mViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageVulnerabilities, damageTypes, R.string.label_damage_vulnerabilities));
|
||||
mViewModel.getDamageResistances().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageResistances, damageTypes, R.string.label_damage_resistances));
|
||||
mViewModel.getDamageImmunities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageImmunities, damageTypes, R.string.label_damage_immunities));
|
||||
mViewModel.getConditionImmunities().observe(getViewLifecycleOwner(), conditionImmunities -> setupOptionalTextView(mHolder.conditionImmunities, conditionImmunities, R.string.label_condition_immunities));
|
||||
mViewModel.getSenses().observe(getViewLifecycleOwner(), senses -> setupOptionalTextView(mHolder.senses, senses, R.string.label_senses));
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> setupOptionalTextView(mHolder.languages, languages, R.string.label_languages));
|
||||
mViewModel.getChallenge().observe(getViewLifecycleOwner(), challengeRating -> setupLabeledTextView(mHolder.challenge, challengeRating, R.string.label_challenge_rating));
|
||||
mViewModel.getAbilities().observe(getViewLifecycleOwner(), abilities -> setupTraitList(mHolder.abilities, abilities));
|
||||
mViewModel.getActions().observe(getViewLifecycleOwner(), actions -> setupTraitList(mHolder.actions, actions, mHolder.actions_label, mHolder.actions_divider));
|
||||
mViewModel.getReactions().observe(getViewLifecycleOwner(), reactions -> setupTraitList(mHolder.reactions, reactions, mHolder.reactions_label, mHolder.reactions_divider));
|
||||
mViewModel.getRegionalEffects().observe(getViewLifecycleOwner(), regionalEffects -> setupTraitList(mHolder.regionalEffects, regionalEffects, mHolder.regionalEffects_label, mHolder.regionalEffects_divider));
|
||||
mViewModel.getLairActions().observe(getViewLifecycleOwner(), lairActions -> setupTraitList(mHolder.lairActions, lairActions, mHolder.lairActions_label, mHolder.lairActions_divider));
|
||||
mViewModel.getLegendaryActions().observe(getViewLifecycleOwner(), legendaryActions -> setupTraitList(mHolder.legendaryActions, legendaryActions, mHolder.legendaryActions_label, mHolder.legendaryActions_divider));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupLabeledTextView(@NonNull TextView view, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
String fullText = String.format("<b>%s</b> %s", title, text);
|
||||
view.setText(Html.fromHtml(fullText));
|
||||
}
|
||||
|
||||
private void setupOptionalTextView(TextView root, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
if (StringHelper.isNullOrEmpty(text)) {
|
||||
root.setVisibility(View.GONE);
|
||||
} else {
|
||||
root.setVisibility(View.VISIBLE);
|
||||
}
|
||||
Spanned formatted;
|
||||
if (StringHelper.isNullOrEmpty(title)) {
|
||||
formatted = Html.fromHtml(text);
|
||||
} else {
|
||||
formatted = Html.fromHtml(String.format("<b>%s</b> %s", title, text));
|
||||
}
|
||||
root.setText(formatted);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits) {
|
||||
setupTraitList(root, traits, null, null);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits, View label, View divider) {
|
||||
int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE;
|
||||
Context context = getContext();
|
||||
DisplayMetrics displayMetrics = null;
|
||||
if (context != null) {
|
||||
Resources resources = context.getResources();
|
||||
if (resources != null) {
|
||||
displayMetrics = resources.getDisplayMetrics();
|
||||
}
|
||||
}
|
||||
root.removeAllViews();
|
||||
for (String action : traits) {
|
||||
TextView tvAction = new TextView(getContext());
|
||||
// TODO: Handle multiline block quotes specially so they stay multiline.
|
||||
// TODO: Replace QuoteSpans in the result of fromHtml with something like this https://stackoverflow.com/questions/7717567/how-to-style-blockquotes-in-android-textviews to make them indent as expected
|
||||
tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
|
||||
tvAction.setLayoutParams(layoutParams);
|
||||
root.addView(tvAction);
|
||||
}
|
||||
root.setVisibility(visibility);
|
||||
if (label != null) {
|
||||
label.setVisibility(visibility);
|
||||
}
|
||||
if (divider != null) {
|
||||
divider.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.monster_detail_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_action_edit_monster) {
|
||||
UUID monsterId = mViewModel.getId().getValue();
|
||||
if (monsterId != null) {
|
||||
NavDirections action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
} else {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final TextView name;
|
||||
final TextView meta;
|
||||
final TextView armorClass;
|
||||
final TextView hitPoints;
|
||||
final TextView speed;
|
||||
final TextView strength;
|
||||
final TextView dexterity;
|
||||
final TextView constitution;
|
||||
final TextView intelligence;
|
||||
final TextView wisdom;
|
||||
final TextView charisma;
|
||||
final TextView savingThrows;
|
||||
final TextView skills;
|
||||
final TextView damageVulnerabilities;
|
||||
final TextView damageResistances;
|
||||
final TextView damageImmunities;
|
||||
final TextView conditionImmunities;
|
||||
final TextView senses;
|
||||
final TextView languages;
|
||||
final TextView challenge;
|
||||
final LinearLayout abilities;
|
||||
final LinearLayout actions;
|
||||
final TextView actions_label;
|
||||
final ImageView actions_divider;
|
||||
final LinearLayout reactions;
|
||||
final TextView reactions_label;
|
||||
final ImageView reactions_divider;
|
||||
final LinearLayout legendaryActions;
|
||||
final TextView legendaryActions_label;
|
||||
final ImageView legendaryActions_divider;
|
||||
final LinearLayout lairActions;
|
||||
final TextView lairActions_label;
|
||||
final ImageView lairActions_divider;
|
||||
final LinearLayout regionalEffects;
|
||||
final TextView regionalEffects_label;
|
||||
final ImageView regionalEffects_divider;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
meta = root.findViewById(R.id.meta);
|
||||
armorClass = root.findViewById(R.id.armorClass);
|
||||
hitPoints = root.findViewById(R.id.hitPoints);
|
||||
speed = root.findViewById(R.id.speed);
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challenge = root.findViewById(R.id.challenge);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
actions_divider = root.findViewById(R.id.actions_divider);
|
||||
actions_label = root.findViewById(R.id.actions_label);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
reactions_divider = root.findViewById(R.id.reactions_divider);
|
||||
reactions_label = root.findViewById(R.id.reactions_label);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
legendaryActions_divider = root.findViewById(R.id.legendaryActions_divider);
|
||||
legendaryActions_label = root.findViewById(R.id.legendaryActions_label);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
lairActions_divider = root.findViewById(R.id.lairActions_divider);
|
||||
lairActions_label = root.findViewById(R.id.lairActions_label);
|
||||
regionalEffects = root.findViewById(R.id.regionalEffects);
|
||||
regionalEffects_divider = root.findViewById(R.id.regionalEffects_divider);
|
||||
regionalEffects_label = root.findViewById(R.id.regionalEffects_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MonsterDetailViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<List<String>> mAbilities;
|
||||
private final MutableLiveData<List<String>> mActions;
|
||||
private final MutableLiveData<String> mArmorClass;
|
||||
private final MutableLiveData<String> mChallenge;
|
||||
private final MutableLiveData<String> mCharisma;
|
||||
private final MutableLiveData<String> mConditionImmunities;
|
||||
private final MutableLiveData<String> mConstitution;
|
||||
private final MutableLiveData<String> mDamageResistances;
|
||||
private final MutableLiveData<String> mDamageImmunities;
|
||||
private final MutableLiveData<String> mDamageVulnerabilities;
|
||||
private final MutableLiveData<String> mDexterity;
|
||||
private final MutableLiveData<String> mHitPoints;
|
||||
private final MutableLiveData<String> mIntelligence;
|
||||
private final MutableLiveData<List<String>> mLairActions;
|
||||
private final MutableLiveData<String> mLanguages;
|
||||
private final MutableLiveData<List<String>> mLegendaryActions;
|
||||
private final MutableLiveData<String> mMeta;
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<List<String>> mReactions;
|
||||
private final MutableLiveData<List<String>> mRegionalEffects;
|
||||
private final MutableLiveData<String> mSavingThrows;
|
||||
private final MutableLiveData<String> mSenses;
|
||||
private final MutableLiveData<String> mSkills;
|
||||
private final MutableLiveData<String> mSpeed;
|
||||
private final MutableLiveData<String> mStrength;
|
||||
private final MutableLiveData<String> mWisdom;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
private Monster mMonster;
|
||||
|
||||
public MonsterDetailViewModel() {
|
||||
mMonster = null;
|
||||
mAbilities = new MutableLiveData<>(new ArrayList<>());
|
||||
mActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mArmorClass = new MutableLiveData<>("");
|
||||
mChallenge = new MutableLiveData<>("");
|
||||
mCharisma = new MutableLiveData<>("");
|
||||
mConditionImmunities = new MutableLiveData<>("");
|
||||
mConstitution = new MutableLiveData<>("");
|
||||
mDamageImmunities = new MutableLiveData<>("");
|
||||
mDamageResistances = new MutableLiveData<>("");
|
||||
mDamageVulnerabilities = new MutableLiveData<>("");
|
||||
mDexterity = new MutableLiveData<>("");
|
||||
mHitPoints = new MutableLiveData<>("");
|
||||
mIntelligence = new MutableLiveData<>("");
|
||||
mLairActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mLanguages = new MutableLiveData<>("");
|
||||
mLegendaryActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mMeta = new MutableLiveData<>("");
|
||||
mName = new MutableLiveData<>("");
|
||||
mReactions = new MutableLiveData<>(new ArrayList<>());
|
||||
mRegionalEffects = new MutableLiveData<>(new ArrayList<>());
|
||||
mSavingThrows = new MutableLiveData<>("");
|
||||
mSenses = new MutableLiveData<>("");
|
||||
mSkills = new MutableLiveData<>("");
|
||||
mSpeed = new MutableLiveData<>("");
|
||||
mStrength = new MutableLiveData<>("");
|
||||
mWisdom = new MutableLiveData<>("");
|
||||
mMonsterId = new MutableLiveData<>(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getAbilities() {
|
||||
return mAbilities;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getActions() {
|
||||
return mActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getReactions() {
|
||||
return mReactions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLegendaryActions() {
|
||||
return mLegendaryActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLairActions() {
|
||||
return mLairActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getRegionalEffects() {
|
||||
return mRegionalEffects;
|
||||
}
|
||||
|
||||
public LiveData<String> getArmorClass() {
|
||||
return mArmorClass;
|
||||
}
|
||||
|
||||
public LiveData<String> getChallenge() {
|
||||
return mChallenge;
|
||||
}
|
||||
|
||||
public LiveData<String> getCharisma() {
|
||||
return mCharisma;
|
||||
}
|
||||
|
||||
public LiveData<String> getConditionImmunities() {
|
||||
return mConditionImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getConstitution() {
|
||||
return mConstitution;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageResistances() {
|
||||
return mDamageResistances;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageImmunities() {
|
||||
return mDamageImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageVulnerabilities() {
|
||||
return mDamageVulnerabilities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDexterity() {
|
||||
return mDexterity;
|
||||
}
|
||||
|
||||
public LiveData<String> getHitPoints() {
|
||||
return mHitPoints;
|
||||
}
|
||||
|
||||
public LiveData<String> getIntelligence() {
|
||||
return mIntelligence;
|
||||
}
|
||||
|
||||
public LiveData<String> getLanguages() {
|
||||
return mLanguages;
|
||||
}
|
||||
|
||||
public LiveData<String> getMeta() {
|
||||
return mMeta;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public LiveData<String> getSavingThrows() {
|
||||
return mSavingThrows;
|
||||
}
|
||||
|
||||
public LiveData<String> getSenses() {
|
||||
return mSenses;
|
||||
}
|
||||
|
||||
public LiveData<String> getSkills() {
|
||||
return mSkills;
|
||||
}
|
||||
|
||||
public LiveData<String> getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
public LiveData<String> getStrength() {
|
||||
return mStrength;
|
||||
}
|
||||
|
||||
public LiveData<String> getWisdom() {
|
||||
return mWisdom;
|
||||
}
|
||||
|
||||
public LiveData<UUID> getId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
|
||||
public void setMonster(@NonNull Monster monster) {
|
||||
mMonster = monster;
|
||||
mAbilities.setValue(mMonster.getAbilityDescriptions());
|
||||
mActions.setValue(mMonster.getActionDescriptions());
|
||||
mArmorClass.setValue(mMonster.getArmorClass());
|
||||
mChallenge.setValue(mMonster.getChallengeRatingDescription());
|
||||
mCharisma.setValue(monster.getCharismaDescription());
|
||||
mConditionImmunities.setValue(mMonster.getConditionImmunitiesDescription());
|
||||
mConstitution.setValue(monster.getConstitutionDescription());
|
||||
mDamageImmunities.setValue(mMonster.getDamageImmunitiesDescription());
|
||||
mDamageResistances.setValue(mMonster.getDamageResistancesDescription());
|
||||
mDamageVulnerabilities.setValue(mMonster.getDamageVulnerabilitiesDescription());
|
||||
mDexterity.setValue(monster.getDexterityDescription());
|
||||
mHitPoints.setValue(mMonster.getHitPoints());
|
||||
mIntelligence.setValue(monster.getIntelligenceDescription());
|
||||
mLairActions.setValue(mMonster.getLairActionDescriptions());
|
||||
mLanguages.setValue(mMonster.getLanguagesDescription());
|
||||
mLegendaryActions.setValue(mMonster.getLegendaryActionDescriptions());
|
||||
mMeta.setValue(mMonster.getMeta());
|
||||
mMonsterId.setValue(mMonster.id);
|
||||
mName.setValue(mMonster.name);
|
||||
mReactions.setValue(monster.getReactionDescriptions());
|
||||
mRegionalEffects.setValue(monster.getRegionalActionDescriptions());
|
||||
mSavingThrows.setValue(monster.getSavingThrowsDescription());
|
||||
mSenses.setValue(monster.getSensesDescription());
|
||||
mSkills.setValue(monster.getSkillsDescription());
|
||||
mSpeed.setValue(mMonster.getSpeedText());
|
||||
mStrength.setValue(monster.getStrengthDescription());
|
||||
mWisdom.setValue(monster.getWisdomDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.library.LibraryFragmentDirections;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MonsterImportFragment extends MCFragment {
|
||||
private ViewHolder mHolder;
|
||||
private MonsterImportViewModel mViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
Logger.logDebug("MonsterCards: loading monster for import");
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
String json = MonsterImportFragmentArgs.fromBundle(arguments).getJson();
|
||||
setHasOptionsMenu(true);
|
||||
Monster monster = MonsterImportHelper.fromJSON(json);
|
||||
mViewModel = new ViewModelProvider(this).get(MonsterImportViewModel.class);
|
||||
mViewModel.setMonster(monster);
|
||||
View root = inflater.inflate(R.layout.fragment_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getName().observe(getViewLifecycleOwner(), mHolder.name::setText);
|
||||
mViewModel.getMeta().observe(getViewLifecycleOwner(), mHolder.meta::setText);
|
||||
mViewModel.getArmorClass().observe(getViewLifecycleOwner(), armorText -> setupLabeledTextView(mHolder.armorClass, armorText, R.string.label_armor_class));
|
||||
mViewModel.getHitPoints().observe(getViewLifecycleOwner(), hitPoints -> setupLabeledTextView(mHolder.hitPoints, hitPoints, R.string.label_hit_points));
|
||||
mViewModel.getSpeed().observe(getViewLifecycleOwner(), speed -> setupLabeledTextView(mHolder.speed, speed, R.string.label_speed));
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), mHolder.strength::setText);
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), mHolder.dexterity::setText);
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), mHolder.constitution::setText);
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), mHolder.intelligence::setText);
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), mHolder.wisdom::setText);
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), mHolder.charisma::setText);
|
||||
mViewModel.getSavingThrows().observe(getViewLifecycleOwner(), savingThrows -> setupOptionalTextView(mHolder.savingThrows, savingThrows, R.string.label_saving_throws));
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> setupOptionalTextView(mHolder.skills, skills, R.string.label_skills));
|
||||
mViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageVulnerabilities, damageTypes, R.string.label_damage_vulnerabilities));
|
||||
mViewModel.getDamageResistances().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageResistances, damageTypes, R.string.label_damage_resistances));
|
||||
mViewModel.getDamageImmunities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageImmunities, damageTypes, R.string.label_damage_immunities));
|
||||
mViewModel.getConditionImmunities().observe(getViewLifecycleOwner(), conditionImmunities -> setupOptionalTextView(mHolder.conditionImmunities, conditionImmunities, R.string.label_condition_immunities));
|
||||
mViewModel.getSenses().observe(getViewLifecycleOwner(), senses -> setupOptionalTextView(mHolder.senses, senses, R.string.label_senses));
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> setupOptionalTextView(mHolder.languages, languages, R.string.label_languages));
|
||||
mViewModel.getChallenge().observe(getViewLifecycleOwner(), challengeRating -> setupLabeledTextView(mHolder.challenge, challengeRating, R.string.label_challenge_rating));
|
||||
mViewModel.getAbilities().observe(getViewLifecycleOwner(), abilities -> setupTraitList(mHolder.abilities, abilities));
|
||||
mViewModel.getActions().observe(getViewLifecycleOwner(), actions -> setupTraitList(mHolder.actions, actions, mHolder.actions_label, mHolder.actions_divider));
|
||||
mViewModel.getReactions().observe(getViewLifecycleOwner(), reactions -> setupTraitList(mHolder.reactions, reactions, mHolder.reactions_label, mHolder.reactions_divider));
|
||||
mViewModel.getRegionalEffects().observe(getViewLifecycleOwner(), regionalEffects -> setupTraitList(mHolder.regionalEffects, regionalEffects, mHolder.regionalEffects_label, mHolder.regionalEffects_divider));
|
||||
mViewModel.getLairActions().observe(getViewLifecycleOwner(), lairActions -> setupTraitList(mHolder.lairActions, lairActions, mHolder.lairActions_label, mHolder.lairActions_divider));
|
||||
mViewModel.getLegendaryActions().observe(getViewLifecycleOwner(), legendaryActions -> setupTraitList(mHolder.legendaryActions, legendaryActions, mHolder.legendaryActions_label, mHolder.legendaryActions_divider));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupLabeledTextView(@NonNull TextView view, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
String fullText = String.format("<b>%s</b> %s", title, text);
|
||||
view.setText(Html.fromHtml(fullText));
|
||||
}
|
||||
|
||||
private void setupOptionalTextView(TextView root, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
if (StringHelper.isNullOrEmpty(text)) {
|
||||
root.setVisibility(View.GONE);
|
||||
} else {
|
||||
root.setVisibility(View.VISIBLE);
|
||||
}
|
||||
Spanned formatted;
|
||||
if (StringHelper.isNullOrEmpty(title)) {
|
||||
formatted = Html.fromHtml(text);
|
||||
} else {
|
||||
formatted = Html.fromHtml(String.format("<b>%s</b> %s", title, text));
|
||||
}
|
||||
root.setText(formatted);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits) {
|
||||
setupTraitList(root, traits, null, null);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits, View label, View divider) {
|
||||
int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE;
|
||||
Context context = getContext();
|
||||
DisplayMetrics displayMetrics = null;
|
||||
if (context != null) {
|
||||
Resources resources = context.getResources();
|
||||
if (resources != null) {
|
||||
displayMetrics = resources.getDisplayMetrics();
|
||||
}
|
||||
}
|
||||
root.removeAllViews();
|
||||
for (String action : traits) {
|
||||
TextView tvAction = new TextView(getContext());
|
||||
// TODO: Handle multiline block quotes specially so they stay multiline.
|
||||
// TODO: Replace QuoteSpans in the result of fromHtml with something like this https://stackoverflow.com/questions/7717567/how-to-style-blockquotes-in-android-textviews to make them indent as expected
|
||||
tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
|
||||
tvAction.setLayoutParams(layoutParams);
|
||||
root.addView(tvAction);
|
||||
}
|
||||
root.setVisibility(visibility);
|
||||
if (label != null) {
|
||||
label.setVisibility(visibility);
|
||||
}
|
||||
if (divider != null) {
|
||||
divider.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.import_monster, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_action_import_monster) {
|
||||
Logger.logDebug("Menu Item Selected");
|
||||
Monster monster = mViewModel.getMonster();
|
||||
if (monster != null) {
|
||||
monster.id = UUID.randomUUID();
|
||||
MonsterCardsApplication application = (MonsterCardsApplication) getApplication();
|
||||
MonsterRepository repository = application.getMonsterRepository();
|
||||
repository.addMonster(monster).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Snackbar.make(
|
||||
mHolder.root,
|
||||
getString(R.string.snackbar_monster_created, monster.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", (_view) -> navigateToEditMonster(monster.id))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
Snackbar.make(mHolder.root, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void navigateToEditMonster(@NonNull UUID monsterId) {
|
||||
NavController navController = Navigation.findNavController(requireView());
|
||||
NavDirections action;
|
||||
action = MonsterImportFragmentDirections.actionMonsterImportFragmentToNavigationLibrary();
|
||||
navController.navigate(action);
|
||||
action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
|
||||
navController.navigate(action);
|
||||
action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
|
||||
navController.navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final View root;
|
||||
final TextView name;
|
||||
final TextView meta;
|
||||
final TextView armorClass;
|
||||
final TextView hitPoints;
|
||||
final TextView speed;
|
||||
final TextView strength;
|
||||
final TextView dexterity;
|
||||
final TextView constitution;
|
||||
final TextView intelligence;
|
||||
final TextView wisdom;
|
||||
final TextView charisma;
|
||||
final TextView savingThrows;
|
||||
final TextView skills;
|
||||
final TextView damageVulnerabilities;
|
||||
final TextView damageResistances;
|
||||
final TextView damageImmunities;
|
||||
final TextView conditionImmunities;
|
||||
final TextView senses;
|
||||
final TextView languages;
|
||||
final TextView challenge;
|
||||
final LinearLayout abilities;
|
||||
final LinearLayout actions;
|
||||
final TextView actions_label;
|
||||
final ImageView actions_divider;
|
||||
final LinearLayout reactions;
|
||||
final TextView reactions_label;
|
||||
final ImageView reactions_divider;
|
||||
final LinearLayout legendaryActions;
|
||||
final TextView legendaryActions_label;
|
||||
final ImageView legendaryActions_divider;
|
||||
final LinearLayout lairActions;
|
||||
final TextView lairActions_label;
|
||||
final ImageView lairActions_divider;
|
||||
final LinearLayout regionalEffects;
|
||||
final TextView regionalEffects_label;
|
||||
final ImageView regionalEffects_divider;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
this.root = root;
|
||||
name = root.findViewById(R.id.name);
|
||||
meta = root.findViewById(R.id.meta);
|
||||
armorClass = root.findViewById(R.id.armorClass);
|
||||
hitPoints = root.findViewById(R.id.hitPoints);
|
||||
speed = root.findViewById(R.id.speed);
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challenge = root.findViewById(R.id.challenge);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
actions_divider = root.findViewById(R.id.actions_divider);
|
||||
actions_label = root.findViewById(R.id.actions_label);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
reactions_divider = root.findViewById(R.id.reactions_divider);
|
||||
reactions_label = root.findViewById(R.id.reactions_label);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
legendaryActions_divider = root.findViewById(R.id.legendaryActions_divider);
|
||||
legendaryActions_label = root.findViewById(R.id.legendaryActions_label);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
lairActions_divider = root.findViewById(R.id.lairActions_divider);
|
||||
lairActions_label = root.findViewById(R.id.lairActions_label);
|
||||
regionalEffects = root.findViewById(R.id.regionalEffects);
|
||||
regionalEffects_divider = root.findViewById(R.id.regionalEffects_divider);
|
||||
regionalEffects_label = root.findViewById(R.id.regionalEffects_label);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MonsterImportViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<String>> mAbilities;
|
||||
private final MutableLiveData<List<String>> mActions;
|
||||
private final MutableLiveData<String> mArmorClass;
|
||||
private final MutableLiveData<String> mChallenge;
|
||||
private final MutableLiveData<String> mCharisma;
|
||||
private final MutableLiveData<String> mConditionImmunities;
|
||||
private final MutableLiveData<String> mConstitution;
|
||||
private final MutableLiveData<String> mDamageResistances;
|
||||
private final MutableLiveData<String> mDamageImmunities;
|
||||
private final MutableLiveData<String> mDamageVulnerabilities;
|
||||
private final MutableLiveData<String> mDexterity;
|
||||
private final MutableLiveData<String> mHitPoints;
|
||||
private final MutableLiveData<String> mIntelligence;
|
||||
private final MutableLiveData<List<String>> mLairActions;
|
||||
private final MutableLiveData<String> mLanguages;
|
||||
private final MutableLiveData<List<String>> mLegendaryActions;
|
||||
private final MutableLiveData<String> mMeta;
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<List<String>> mReactions;
|
||||
private final MutableLiveData<List<String>> mRegionalEffects;
|
||||
private final MutableLiveData<String> mSavingThrows;
|
||||
private final MutableLiveData<String> mSenses;
|
||||
private final MutableLiveData<String> mSkills;
|
||||
private final MutableLiveData<String> mSpeed;
|
||||
private final MutableLiveData<String> mStrength;
|
||||
private final MutableLiveData<String> mWisdom;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
private Monster mMonster;
|
||||
|
||||
public MonsterImportViewModel() {
|
||||
mMonster = null;
|
||||
mAbilities = new MutableLiveData<>(new ArrayList<>());
|
||||
mActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mArmorClass = new MutableLiveData<>("");
|
||||
mChallenge = new MutableLiveData<>("");
|
||||
mCharisma = new MutableLiveData<>("");
|
||||
mConditionImmunities = new MutableLiveData<>("");
|
||||
mConstitution = new MutableLiveData<>("");
|
||||
mDamageImmunities = new MutableLiveData<>("");
|
||||
mDamageResistances = new MutableLiveData<>("");
|
||||
mDamageVulnerabilities = new MutableLiveData<>("");
|
||||
mDexterity = new MutableLiveData<>("");
|
||||
mHitPoints = new MutableLiveData<>("");
|
||||
mIntelligence = new MutableLiveData<>("");
|
||||
mLairActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mLanguages = new MutableLiveData<>("");
|
||||
mLegendaryActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mMeta = new MutableLiveData<>("");
|
||||
mName = new MutableLiveData<>("");
|
||||
mReactions = new MutableLiveData<>(new ArrayList<>());
|
||||
mRegionalEffects = new MutableLiveData<>(new ArrayList<>());
|
||||
mSavingThrows = new MutableLiveData<>("");
|
||||
mSenses = new MutableLiveData<>("");
|
||||
mSkills = new MutableLiveData<>("");
|
||||
mSpeed = new MutableLiveData<>("");
|
||||
mStrength = new MutableLiveData<>("");
|
||||
mWisdom = new MutableLiveData<>("");
|
||||
mMonsterId = new MutableLiveData<>(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getAbilities() {
|
||||
return mAbilities;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getActions() {
|
||||
return mActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getReactions() {
|
||||
return mReactions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLegendaryActions() {
|
||||
return mLegendaryActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLairActions() {
|
||||
return mLairActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getRegionalEffects() {
|
||||
return mRegionalEffects;
|
||||
}
|
||||
|
||||
public LiveData<String> getArmorClass() {
|
||||
return mArmorClass;
|
||||
}
|
||||
|
||||
public LiveData<String> getChallenge() {
|
||||
return mChallenge;
|
||||
}
|
||||
|
||||
public LiveData<String> getCharisma() {
|
||||
return mCharisma;
|
||||
}
|
||||
|
||||
public LiveData<String> getConditionImmunities() {
|
||||
return mConditionImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getConstitution() {
|
||||
return mConstitution;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageResistances() {
|
||||
return mDamageResistances;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageImmunities() {
|
||||
return mDamageImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageVulnerabilities() {
|
||||
return mDamageVulnerabilities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDexterity() {
|
||||
return mDexterity;
|
||||
}
|
||||
|
||||
public LiveData<String> getHitPoints() {
|
||||
return mHitPoints;
|
||||
}
|
||||
|
||||
public LiveData<String> getIntelligence() {
|
||||
return mIntelligence;
|
||||
}
|
||||
|
||||
public LiveData<String> getLanguages() {
|
||||
return mLanguages;
|
||||
}
|
||||
|
||||
public LiveData<String> getMeta() {
|
||||
return mMeta;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public LiveData<String> getSavingThrows() {
|
||||
return mSavingThrows;
|
||||
}
|
||||
|
||||
public LiveData<String> getSenses() {
|
||||
return mSenses;
|
||||
}
|
||||
|
||||
public LiveData<String> getSkills() {
|
||||
return mSkills;
|
||||
}
|
||||
|
||||
public LiveData<String> getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
public LiveData<String> getStrength() {
|
||||
return mStrength;
|
||||
}
|
||||
|
||||
public LiveData<String> getWisdom() {
|
||||
return mWisdom;
|
||||
}
|
||||
|
||||
public LiveData<UUID> getId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
|
||||
public Monster getMonster() {
|
||||
return mMonster;
|
||||
}
|
||||
|
||||
public void setMonster(@NonNull Monster monster) {
|
||||
mMonster = monster;
|
||||
mAbilities.setValue(mMonster.getAbilityDescriptions());
|
||||
mActions.setValue(mMonster.getActionDescriptions());
|
||||
mArmorClass.setValue(mMonster.getArmorClass());
|
||||
mChallenge.setValue(mMonster.getChallengeRatingDescription());
|
||||
mCharisma.setValue(monster.getCharismaDescription());
|
||||
mConditionImmunities.setValue(mMonster.getConditionImmunitiesDescription());
|
||||
mConstitution.setValue(monster.getConstitutionDescription());
|
||||
mDamageImmunities.setValue(mMonster.getDamageImmunitiesDescription());
|
||||
mDamageResistances.setValue(mMonster.getDamageResistancesDescription());
|
||||
mDamageVulnerabilities.setValue(mMonster.getDamageVulnerabilitiesDescription());
|
||||
mDexterity.setValue(monster.getDexterityDescription());
|
||||
mHitPoints.setValue(mMonster.getHitPoints());
|
||||
mIntelligence.setValue(monster.getIntelligenceDescription());
|
||||
mLairActions.setValue(mMonster.getLairActionDescriptions());
|
||||
mLanguages.setValue(mMonster.getLanguagesDescription());
|
||||
mLegendaryActions.setValue(mMonster.getLegendaryActionDescriptions());
|
||||
mMeta.setValue(mMonster.getMeta());
|
||||
mMonsterId.setValue(mMonster.id);
|
||||
mName.setValue(mMonster.name);
|
||||
mReactions.setValue(monster.getReactionDescriptions());
|
||||
mRegionalEffects.setValue(monster.getRegionalActionDescriptions());
|
||||
mSavingThrows.setValue(monster.getSavingThrowsDescription());
|
||||
mSenses.setValue(monster.getSensesDescription());
|
||||
mSkills.setValue(monster.getSkillsDescription());
|
||||
mSpeed.setValue(mMonster.getSpeedText());
|
||||
mStrength.setValue(monster.getStrengthDescription());
|
||||
mWisdom.setValue(monster.getWisdomDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.majinnaibu.monstercards.ui.search;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class SearchFragment extends MCFragment {
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_search, container, false);
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
SearchResultsRecyclerViewAdapter adapter = new SearchResultsRecyclerViewAdapter(repository, null);
|
||||
final RecyclerView recyclerView = root.findViewById(R.id.monster_list);
|
||||
assert recyclerView != null;
|
||||
setupRecyclerView(recyclerView, adapter);
|
||||
|
||||
final TextView textView = root.findViewById(R.id.search_query);
|
||||
textView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
adapter.doSearch(textView.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @NonNull SearchResultsRecyclerViewAdapter adapter) {
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.majinnaibu.monstercards.ui.search;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
public class SearchResultsRecyclerViewAdapter extends RecyclerView.Adapter<SearchResultsRecyclerViewAdapter.ViewHolder> {
|
||||
private final MonsterRepository mRepository;
|
||||
private final ItemCallback mOnClickHandler;
|
||||
private String mSearchText;
|
||||
private List<Monster> mValues;
|
||||
private Disposable mSubscriptionHandler;
|
||||
|
||||
public SearchResultsRecyclerViewAdapter(MonsterRepository repository,
|
||||
ItemCallback onClick) {
|
||||
mRepository = repository;
|
||||
mSearchText = "";
|
||||
mValues = new ArrayList<>();
|
||||
mOnClickHandler = onClick;
|
||||
mSubscriptionHandler = null;
|
||||
|
||||
doSearch(mSearchText);
|
||||
}
|
||||
|
||||
public void doSearch(String searchText) {
|
||||
if (mSubscriptionHandler != null && !mSubscriptionHandler.isDisposed()) {
|
||||
mSubscriptionHandler.dispose();
|
||||
}
|
||||
mSearchText = searchText;
|
||||
Flowable<List<Monster>> foundMonsters = mRepository.searchMonsters(mSearchText);
|
||||
mSubscriptionHandler = foundMonsters.subscribe(monsters -> {
|
||||
mValues = monsters;
|
||||
notifyDataSetChanged();
|
||||
},
|
||||
throwable -> Logger.logError("Error performing search", throwable));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.monster_list_content, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
Monster monster = mValues.get(position);
|
||||
holder.mContentView.setText(monster.name);
|
||||
holder.itemView.setTag(monster);
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (mOnClickHandler != null) {
|
||||
mOnClickHandler.onItem(monster);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItem(Monster monster);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView mContentView;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
mContentView = view.findViewById(R.id.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class ChangeTrackedViewModel extends ViewModel {
|
||||
private final MutableLiveData<Boolean> mHasChanges;
|
||||
|
||||
public ChangeTrackedViewModel() {
|
||||
mHasChanges = new MutableLiveData<>(false);
|
||||
}
|
||||
|
||||
public boolean hasChanges() {
|
||||
Boolean value = mHasChanges.getValue();
|
||||
return value != null && value;
|
||||
}
|
||||
|
||||
protected void makeDirty() {
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
protected void makeClean() {
|
||||
mHasChanges.setValue(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
|
||||
public class MCFragment extends Fragment {
|
||||
public MonsterCardsApplication getApplication() {
|
||||
return (MonsterCardsApplication) requireActivity().getApplication();
|
||||
}
|
||||
|
||||
protected MonsterRepository getMonsterRepository() {
|
||||
return this.getApplication().getMonsterRepository();
|
||||
}
|
||||
|
||||
public AppCompatActivity requireAppCompatActivity() {
|
||||
return (AppCompatActivity) requireActivity();
|
||||
}
|
||||
|
||||
public void setTitle(CharSequence title) {
|
||||
Activity activity = requireActivity();
|
||||
if (activity instanceof AppCompatActivity) {
|
||||
AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
|
||||
ActionBar supportActionBar = appCompatActivity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setTitle(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
|
||||
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
|
||||
|
||||
private final Drawable icon;
|
||||
private final ColorDrawable background;
|
||||
private final Paint clearPaint;
|
||||
private final OnSwipeCallback mOnDelete;
|
||||
private final OnMoveCallback mOnMove;
|
||||
private final Context mContext;
|
||||
|
||||
public SwipeToDeleteCallback(@NonNull Context context, OnSwipeCallback onDelete, OnMoveCallback onMove) {
|
||||
super(onMove == null ? 0 : ItemTouchHelper.UP | ItemTouchHelper.DOWN, onDelete == null ? 0 : ItemTouchHelper.LEFT);
|
||||
mOnDelete = onDelete;
|
||||
mOnMove = onMove;
|
||||
mContext = context;
|
||||
icon = ContextCompat.getDrawable(mContext, R.drawable.ic_delete_white_36);
|
||||
background = new ColorDrawable(context.getResources().getColor(R.color.red));
|
||||
clearPaint = new Paint();
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder target
|
||||
) {
|
||||
if (mOnMove != null) {
|
||||
int from = viewHolder.getAdapterPosition();
|
||||
int to = target.getAdapterPosition();
|
||||
return mOnMove.onMove(from, to);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
if (mOnDelete != null) {
|
||||
int position = viewHolder.getAdapterPosition();
|
||||
mOnDelete.onSwipe(position, direction);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
View itemView = viewHolder.itemView;
|
||||
int itemHeight = itemView.getBottom() - itemView.getTop();
|
||||
boolean isCancelled = dX == 0 && !isCurrentlyActive;
|
||||
|
||||
if (isCancelled) {
|
||||
c.drawRect(itemView.getRight() + dX, itemView.getTop(), itemView.getRight(), itemView.getBottom(), clearPaint);
|
||||
return;
|
||||
}
|
||||
// Draw the red delete background
|
||||
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
|
||||
background.draw(c);
|
||||
|
||||
// Calculate position of delete icon
|
||||
int iconHeight = icon.getIntrinsicHeight();
|
||||
int iconWidth = icon.getIntrinsicWidth();
|
||||
int iconTop = itemView.getTop() + (itemHeight - iconHeight) / 2;
|
||||
int iconMargin = (itemHeight - iconHeight) / 2;
|
||||
int iconLeft = itemView.getRight() - iconMargin - iconWidth;
|
||||
int iconRight = itemView.getRight() - iconMargin;
|
||||
int iconBottom = iconTop + iconHeight;
|
||||
|
||||
// Draw the icon
|
||||
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
|
||||
icon.draw(c);
|
||||
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
public interface OnSwipeCallback {
|
||||
void onSwipe(int position, int direction);
|
||||
}
|
||||
|
||||
public interface OnMoveCallback {
|
||||
boolean onMove(int from, int to);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user