240 Commits

Author SHA1 Message Date
70b7a4e795 Makes DashboardFragment use its ViewModel 2025-07-06 20:12:45 -07:00
8b4d9b6b17 Fixes subscribing in the DashboardFragment. 2025-07-06 20:12:45 -07:00
6bad5a4ed5 Removes unneeded cast in LibraryFragment. 2025-07-06 20:12:44 -07:00
93bf987bb9 Extracts the ViewHolder for SimpleListItemBinding to SimpleListItemViewHolder<T>. 2025-07-06 20:12:44 -07:00
be433bf217 Makes the Library fragment use SimpleListItemBinding to build its ViewHolder. 2025-07-06 20:12:44 -07:00
1ae81b03b0 Makes search work. 2025-07-06 20:12:44 -07:00
4633a50bf4 Layout cleanup. 2025-07-06 20:12:43 -07:00
9ac73337af Replaces duplicated list item layouts with a single shared SimpleListItem. 2025-07-06 20:12:43 -07:00
25340cb7fd Adds generic ItemCallback interface. 2025-07-06 20:12:42 -07:00
be544f5304 Refactors Library to work like the other recycler views. 2025-07-06 20:12:41 -07:00
3dc39ad6a4 cleanup 2025-07-06 20:12:41 -07:00
92ea5b6a0d Moves AppDatabase initialization into the AppDatabase class. 2025-07-06 20:12:40 -07:00
a5eec5c4c8 Moves DiffCallback methods into Monster class. 2025-07-06 20:12:40 -07:00
e598b2984a Adds proguard config. 2025-07-06 20:12:39 -07:00
Tom Hicks
32ae8461b7 Adds functional dashboard based on recycler view that picks the number of columns based on screen width. 2025-07-06 20:12:39 -07:00
Tom Hicks
8686dbdaea Adds dashboard mockup. 2025-07-06 20:12:38 -07:00
Tom Hicks
a8df37325f Adds views for dashboard. 2025-07-06 20:12:38 -07:00
Tom Hicks
2743a8337b Makes radio buttons suck less.
Styles the advantage picker and proficiency picker.
Makes the saving throws screen nicer.
2025-07-06 20:12:37 -07:00
Tom Hicks
b4767676d2 Fixes lists to use similar sized items. 2025-07-06 20:12:37 -07:00
Tom Hicks
7c9037af84 Makes monster importing a fragment in the main activity.
Makes the edit action work when editing an imported monster.
2025-07-06 20:12:36 -07:00
Tom Hicks
c582ba5eaa Adds import monster activity. 2025-07-06 20:12:35 -07:00
Tom Hicks
8b2ab8f48f Adds OnMoveCallback to the SwipeToDeleteCallback class.
Makes traits orderable.
2025-07-06 20:12:34 -07:00
Tom Hicks
2de07c54cc View cleanup.
Makes more numeric fields Steppers.
Sets titles for fragments.
Sets focus on the first EditText on most edit pages.
Makes Checkbox be a SwitchCompat
Fixes monster list item view.
2025-07-06 20:12:33 -07:00
Tom Hicks
c50f79c273 Renames EditTraitListFragment to EditTraitsFragment to match the other list fragments. 2025-07-06 20:12:32 -07:00
Tom Hicks
921a02f953 Replaces condition immunities, damage immunities, damage resistances, damage vulnerabilities, and senses with a unified list of strings editor. 2025-07-06 20:12:30 -07:00
Tom Hicks
ec712842ca Adds editing support for other traits using some shared fragments. 2025-07-06 20:11:56 -07:00
Tom Hicks
2f9dec4d02 Adds ability editor. 2025-07-06 20:11:55 -07:00
Tom Hicks
cb1e3343c9 Adds ability to edit languages. 2025-07-06 20:11:54 -07:00
5bded410d2 ViewModel refactoring. 2025-07-06 20:11:53 -07:00
f6a032844c Fixes default values for array/set properties of monsters in db.
Removes individual senses properties and replaces with a Set<String>.
2025-07-06 20:11:52 -07:00
d214d615e5 Adds edit skill fragment to edit individual skills. 2025-07-06 20:11:51 -07:00
54f863ee5f Adds ability score picker. 2025-07-06 20:11:51 -07:00
d4d298fcc3 Disables annoying lint rule. 2025-07-06 20:11:50 -07:00
7f31d98d7e Adds "New Skill" button. 2025-07-06 20:11:49 -07:00
68348b18c2 Adds edit skills view. 2025-07-06 20:11:49 -07:00
42a2994f2c Adds edit challenge rating screen. 2025-07-06 20:11:49 -07:00
218f39f6c2 Adds better change tracking to know if a monster needs to be saved. 2025-07-06 20:11:48 -07:00
2512bd8d75 Adds saving throws editor. 2025-07-06 20:11:47 -07:00
370d416a23 Adds AdvantagePicker and ProficiencyPicker components. 2025-07-06 20:11:47 -07:00
60d139078b Adds a Stepper control and uses it for the steppers in the editor. 2025-07-06 20:11:46 -07:00
611fa6c323 Adds screen to edit ability scores. 2025-07-06 20:11:45 -07:00
ab5a3c7c67 Adds Edit Speed screen to the monster editor. 2025-07-06 20:11:44 -07:00
b889857e80 Adds Edit Armor screen to edit a monster's armor stats. 2025-07-06 20:11:44 -07:00
da0e072a45 Adds confirmation when going up from the edit monster screen to save, cancel, or discard changes. 2025-07-06 20:11:43 -07:00
e075fc4369 Adds edit basic info screen with most string fields.
Cleans up fonts/margins on edit screens.
Makes the EditMonsterViewModel shared between edit monster fragments.
2025-07-06 20:11:43 -07:00
0db46ebb51 Adds TextChangedListener helper class to make working with text inputs less verbose. 2025-07-06 20:11:42 -07:00
20318b0ad5 Adds top level items to the edit monster view. 2025-07-06 20:11:41 -07:00
6debb1eb27 Replaces EditMonsterFragment with a basic ConstraintLayout. 2025-07-06 20:11:41 -07:00
87c845bd0d Adds Edit Monster placeholder fragment. 2025-07-06 20:11:40 -07:00
f507f9d7cd Adds edit button to monster detail view. 2025-07-06 20:11:40 -07:00
8bb1f64e9f Makes the swipe to delete callback more generic. 2025-07-06 20:11:39 -07:00
f2d0e93911 Fixes a bunch of lint errors and enables Flipper navigation logging. 2025-07-06 20:11:38 -07:00
c9a7e028ae Renames MonsterFragment to MonsterDetailFragment to better explain its use. 2025-07-06 20:11:38 -07:00
807871fe5c Make AppCenter only included in debug builds.
Moves Flipper initialization to the same place as AppCenter.
2025-07-06 20:11:37 -07:00
acadf2170c Adds functional search using sqlite full text search syntax. 2025-07-06 20:11:36 -07:00
8215d2021c Adds DevContent class with dev resources. Specifically an example monster.
Adds a task.
2025-07-06 20:11:35 -07:00
0cbf6022c4 Adds swipe to delete monsters on the library screen. 2025-07-06 20:11:35 -07:00
eec695bfc8 Adds MonsterRepository to manage access to the RoomDB store. 2025-07-06 20:11:34 -07:00
8363912e53 Adds logger. 2025-07-06 20:11:33 -07:00
67db8d79d0 Adds application class. 2025-07-06 20:11:32 -07:00
b5834f3db2 Migrates Monster class to be storable in roomdb. 2025-07-06 20:11:25 -07:00
ca6a319bd9 Creates initial app database class and adds minimal DTO support for monsters. 2025-07-06 20:08:58 -07:00
95ba20b5c6 Adds RoomDB and rxjava3 dependencies. 2025-07-06 20:08:57 -07:00
ea65692b38 Adds abilities to monster cards.
Adds CommonMark dependency and CommonMarkHelper to render it to html.
2025-07-06 20:08:56 -07:00
d7cf01e30d Adds languages to monster cards. 2025-07-06 20:08:56 -07:00
30c6dc7ee5 Adds skills to monster cards. 2025-07-06 20:08:55 -07:00
f13be2c1ac Adds armor class and section divider to monster cards. 2025-07-06 20:08:54 -07:00
920344b5fd Adds monster meta (size, type, subtype/tag, and alignment) to monster cards. 2025-07-06 20:08:54 -07:00
7b3d6003d4 Adds monster name to monster cards. 2025-07-06 20:08:54 -07:00
c837c19b87 Adds Monster fragment to view a monster's card.
Adds query box and search button to search fragment.
Makes the search button show a monster card.
2025-07-06 20:08:53 -07:00
b9759f6364 Adds top level navigation and placeholder fragments. 2025-07-06 20:08:52 -07:00
f24f1d978c Adds debug and release directories to git ignore list. 2025-07-06 20:08:51 -07:00
2defd1fca1 Adds INTERNET permission to manifest. 2025-07-06 20:08:51 -07:00
07f81a5f6d Initial 2025-07-06 20:08:51 -07:00
985c2fb730 Updates Android Gradle Plugin to 7.0.1. 2025-07-06 20:05:13 -07:00
0dc96a8c45 Removes unneeded logging. 2025-07-06 20:05:13 -07:00
10bca503e5 Adds support for virtual file urls. 2025-07-06 20:05:13 -07:00
1d335e9a37 Updates to build on arm macs. 2025-07-06 20:05:13 -07:00
fbf119fb8a Updates manifest to clear warnings and errors for targeting os 12. 2025-07-06 20:05:13 -07:00
3d6adaad2c Updates dependencies to latest versions. 2025-07-06 20:05:13 -07:00
c3a972571a Upgrades Android Gradle plugin to 7.0.0. 2025-07-06 20:05:13 -07:00
4e45a547f4 Moves gradle dependency versions out of variables. 2025-07-06 20:05:13 -07:00
792628d4a4 Adds proguard config. 2025-07-06 20:05:13 -07:00
b6b669a0db Disables release build minification and obfuscation. 2025-07-06 20:05:12 -07:00
540a0474da Disables minification of release builds. 2025-07-06 20:05:12 -07:00
49734d5eef Fixed release flipper stub.
Updates dependency versions.
2025-07-06 20:05:12 -07:00
b2c3728e9a Upgrades android gradle plugin to 4.2.2. 2025-07-06 20:05:12 -07:00
39cab7f799 Removes dead code and cleans up annotations Nullable/NonNull annotations. 2025-07-06 20:05:12 -07:00
Tom Hicks
1e007a3553 Makes dashboard views use dimension and string resources. 2025-07-06 20:05:12 -07:00
Tom Hicks
1a487f950d Adds functional dashboard based on recycler view that picks the number of columns based on screen width. 2025-07-06 20:05:12 -07:00
Tom Hicks
ea13e38402 Adds dashboard mockup. 2025-07-06 20:05:12 -07:00
Tom Hicks
8e2372085d Adds views for dashboard. 2025-07-06 20:05:10 -07:00
Tom Hicks
129d910126 Makes radio buttons suck less.
Styles the advantage picker and proficiency picker.
Makes the saving throws screen nicer.
2025-07-06 20:05:09 -07:00
Tom Hicks
1a23e5e35a Fixes missing descriptions on imported traits. 2025-07-06 20:05:09 -07:00
Tom Hicks
660cf633da Fixes lists to use similar sized items. 2025-07-06 20:05:09 -07:00
Tom Hicks
1e7a7c68aa Fixes screen titles. 2025-07-06 20:05:08 -07:00
Tom Hicks
070fda0989 Makes monster importing a fragment in the main activity.
Makes the edit action work when editing an imported monster.
2025-07-06 20:05:08 -07:00
Tom Hicks
af05c41b75 Adds import monster activity. 2025-07-06 20:05:08 -07:00
Tom Hicks
efa4c2a299 Refactors monster helper methods into a separate class. 2025-07-06 20:05:07 -07:00
Tom Hicks
2f5918b7a2 Adds other traits to the monster detail fragment.
Cleans up the monster detail fragment to work like the other fragments.
2025-07-06 20:05:07 -07:00
Tom Hicks
cb6f7122ed Refactors monster detail to use a view holder and partially corrects Regional Actions to Regional Effects. 2025-07-06 20:05:07 -07:00
Tom Hicks
7cfc6d4f65 Removes unused methods from EditMonsterViewModel. 2025-07-06 20:05:07 -07:00
Tom Hicks
cdae6a8b39 Adds OnMoveCallback to the SwipeToDeleteCallback class.
Makes traits orderable.
2025-07-06 20:05:07 -07:00
Tom Hicks
9d46d1420e Removes todo. 2025-07-06 20:05:07 -07:00
Tom Hicks
6d43b0635c View cleanup.
Makes more numeric fields Steppers.
Sets titles for fragments.
Sets focus on the first EditText on most edit pages.
Makes Checkbox be a SwitchCompat
Fixes monster list item view.
2025-07-06 20:05:07 -07:00
Tom Hicks
4c138ee499 Fix Stepper initial values. 2025-07-06 20:05:07 -07:00
Tom Hicks
0fcfa7e782 Adds ScrollViews to the editors that were missing them. 2025-07-06 20:05:07 -07:00
Tom Hicks
791cf4164c Fixes search.
It now works the same as iOS. It case insensitive matches the entire search text in any of name, size, type, subtype, or alignment.
2025-07-06 20:05:07 -07:00
Tom Hicks
fb12deaa3e Fixes removing languages. 2025-07-06 20:05:07 -07:00
Tom Hicks
6e597462ef Adds some TODOs. 2025-07-06 20:05:07 -07:00
Tom Hicks
12ffc5b15f Fixes the size of the edit languages header. 2025-07-06 20:05:07 -07:00
Tom Hicks
9c81bd4905 Fixes selection bug in AbilityScorePicker. 2025-07-06 20:05:07 -07:00
Tom Hicks
f1cbc60857 Renames EditTraitListFragment to EditTraitsFragment to match the other list fragments. 2025-07-06 20:05:07 -07:00
Tom Hicks
a2798ddc82 Replaces condition immunities, damage immunities, damage resistances, damage vulnerabilities, and senses with a unified list of strings editor. 2025-07-06 20:05:06 -07:00
Tom Hicks
ecf2b01723 Auto formatted all files. 2025-07-06 20:05:05 -07:00
Tom Hicks
18d6f2a31e Adds editing support for other traits using some shared fragments. 2025-07-06 20:05:05 -07:00
Tom Hicks
7cbcf8d07c Adds ability editor. 2025-07-06 20:05:04 -07:00
Tom Hicks
dc487d238a Fixes name of "Add Damage Type" string. 2025-07-06 20:05:03 -07:00
Tom Hicks
b8c702f665 Code style cleanup.
Replaces Fragment with MCFragment.
2025-07-06 20:05:03 -07:00
Tom Hicks
259b59f519 Adds equals override to Trait objects.
Fixes trait comparisons.
2025-07-06 20:05:03 -07:00
Tom Hicks
5289bac908 Adds ability to edit languages. 2025-07-06 20:05:03 -07:00
Tom Hicks
34e11d97e5 Makes use of class compareTo methods when sorting skills and languages.
Fixes dirty state of skills when modifying them.
2025-07-06 20:05:03 -07:00
Tom Hicks
ca6684a093 Replaces manual view != null assertions with requireView. 2025-07-06 20:05:03 -07:00
Tom Hicks
6e48a6f455 Adds equals override for Language objects.
Makes languages sort group by can speaks.
2025-07-06 20:05:03 -07:00
b8af70406f Add damage resistances and damage vulnerabilities to the monster editor. 2025-07-06 20:05:03 -07:00
7eae6f820e Adds damage resistances to monster editor. 2025-07-06 20:05:03 -07:00
2076d53b11 ViewModel refactoring. 2025-07-06 20:05:03 -07:00
e20602cc3d Adds editing of condition immunities. 2025-07-06 20:05:03 -07:00
04dc066191 Adds getters and setters to EditMonsterViewModel for Languages, Abilities, Actions, Reactions, Lair Actions, Legendary Actions, and Regional Actions. 2025-07-06 20:05:03 -07:00
72b3df429f Adds getters and setters for string array properties of EditMonsterViewModel. 2025-07-06 20:05:03 -07:00
8d94afeb55 Adds edit sense fragment. 2025-07-06 20:05:03 -07:00
8bae59ed29 Edit skill cleanup. 2025-07-06 20:05:03 -07:00
c401b7919e Adds Edit Senses fragment. 2025-07-06 20:05:03 -07:00
9983ba10cb Removes unused code from edit skills. 2025-07-06 20:05:03 -07:00
171bc7436e Replaces usages of Fragment with MCFragment. 2025-07-06 20:05:03 -07:00
04a30aa766 Adds senses to EditMonsterViewModel. 2025-07-06 20:05:03 -07:00
2356726e3f Fixes default values for array/set properties of monsters in db.
Removes individual senses properties and replaces with a Set<String>.
2025-07-06 20:05:03 -07:00
6151ad889c Updates TODO in EditMonsterViewModel. 2025-07-06 20:05:03 -07:00
1f15d73573 Adds edit skill fragment to edit individual skills. 2025-07-06 20:05:03 -07:00
40589f171d Adds stubbed out method to navigate to the edit skill fragment.
Adds a click handler to the edit skills recycler view adapter.
Makes tapping a skill in the edit skills view navigate to the editor for that skill.
Makes adding a new skill immediately edit the skill.
2025-07-06 20:05:02 -07:00
a58c851240 Makes addNewSkill return the new skill.
Adds replaceSkill to replace an existing skill in the skills array.
2025-07-06 20:05:02 -07:00
2c6b514538 Adds an equals method to skills. 2025-07-06 20:05:02 -07:00
f6a8b83343 Makes AdvantagePicker work if you set the id of the advantage picker to advantage. 2025-07-06 20:05:02 -07:00
eb3fa5108f Adds ability score picker. 2025-07-06 20:05:02 -07:00
922db42322 Lint cleanup. 2025-07-06 20:05:01 -07:00
71177b92f9 Removes debug logging. 2025-07-06 20:05:01 -07:00
34e68443ae Disables annoying lint rule. 2025-07-06 20:05:01 -07:00
857733ec9c Adds ability to remove a skill. 2025-07-06 20:05:00 -07:00
7e78ad8b7d Keeps skills on the edit skills screen sorted. 2025-07-06 20:05:00 -07:00
430fa61be1 Fixes bug where adding a new skill wouldn't show up until leaving and returning to the edit skills screen. 2025-07-06 20:05:00 -07:00
70e05cbb21 Adds item dividers to the Library and Edit Skills screens.
Makes the two layouts consistent with each other.
2025-07-06 20:05:00 -07:00
b67073622f Makes Add Skill button a FloatingActionButton. 2025-07-06 20:05:00 -07:00
c90579903d Adds "New Skill" button. 2025-07-06 20:05:00 -07:00
1a02eab07a Adds edit skills view. 2025-07-06 20:05:00 -07:00
00463c8092 Adds edit challenge rating screen. 2025-07-06 20:05:00 -07:00
153c49fe7b Adds remaining monster properties to the edit monster view model. 2025-07-06 20:04:59 -07:00
8178ec6fd7 Adds better change tracking to know if a monster needs to be saved. 2025-07-06 20:04:59 -07:00
2e7e40554d Adds saving throws editor. 2025-07-06 20:04:58 -07:00
989440de83 Adds AdvantagePicker and ProficiencyPicker components. 2025-07-06 20:04:57 -07:00
3c4adacc17 Adds a Stepper control and uses it for the steppers in the editor. 2025-07-06 20:04:57 -07:00
c28e1cb8c5 Adds screen to edit ability scores. 2025-07-06 20:04:56 -07:00
48dab535e9 Adds missed change tracking to EditMonsterViewModel. 2025-07-06 20:04:55 -07:00
ff26cb64b7 Adds Edit Speed screen to the monster editor. 2025-07-06 20:04:55 -07:00
28f0787020 Adds Edit Armor screen to edit a monster's armor stats. 2025-07-06 20:04:55 -07:00
71da064423 Adds hit dice and has custom HP toggle to the edit basic info screen. 2025-07-06 20:04:54 -07:00
23bcdc237d Adds confirmation when going up from the edit monster screen to save, cancel, or discard changes. 2025-07-06 20:04:54 -07:00
b56a662c9e Adds setTitle helper method to MCFragment. 2025-07-06 20:04:53 -07:00
dcce64f91a Adds edit basic info screen with most string fields.
Cleans up fonts/margins on edit screens.
Makes the EditMonsterViewModel shared between edit monster fragments.
2025-07-06 20:04:53 -07:00
595ee0c6fb Adds TextChangedListener helper class to make working with text inputs less verbose. 2025-07-06 20:04:52 -07:00
d52102d430 Makes EditMonsterFragment load the monster based with the id passed in and show it's name in the title. 2025-07-06 20:04:52 -07:00
6a4abdd547 Fixes passing the monster id when navigating to the edit monster screen. 2025-07-06 20:04:52 -07:00
bd3741af2d Extracts library strings to resources. 2025-07-06 20:04:52 -07:00
e254adfdce Changes to code format settings. 2025-07-06 20:04:52 -07:00
9c973ef348 Adds helper method to MCFragment to get the AppCompatActivity for the fragment. 2025-07-06 20:04:52 -07:00
dc9a0827d4 Adds edit monster view holder and placeholder for basic info tap handler. 2025-07-06 20:04:52 -07:00
b27274928e Adds top level items to the edit monster view. 2025-07-06 20:04:52 -07:00
6bb1e419c8 Replaces EditMonsterFragment with a basic ConstraintLayout. 2025-07-06 20:04:52 -07:00
3cda90eedd Adds shared preferences flipper plugin. 2025-07-06 20:04:51 -07:00
98a7dc5eeb Migrates from jcenter to maven central because of the jcenter shutdown. 2025-07-06 20:04:51 -07:00
e384e29570 Upgrades Android Gradle Plugin to 4.2.1 and Gradle to 6.7.1 2025-07-06 20:04:51 -07:00
7aa6419ece Adds Edit Monster placeholder fragment. 2025-07-06 20:04:51 -07:00
6b953e320d Adds edit button to monster detail view. 2025-07-06 20:04:50 -07:00
9f56f0283a Fixes the width of items in the library view. 2025-07-06 20:04:50 -07:00
b5f92afae9 Project file updates. 2025-07-06 20:04:50 -07:00
f58243ef6b Library refactoring. 2025-07-06 20:04:50 -07:00
886778ee78 Makes the swipe to delete callback more generic. 2025-07-06 20:04:50 -07:00
0a85324734 Fixes a bunch of lint errors and enables Flipper navigation logging. 2025-07-06 20:04:49 -07:00
b374dbfe71 Replaces fragment tag with FragmentContainerView. 2025-07-06 20:04:48 -07:00
21af6e20ba Renames MonsterFragment to MonsterDetailFragment to better explain its use. 2025-07-06 20:04:48 -07:00
dc9066daca Make AppCenter only included in debug builds.
Moves Flipper initialization to the same place as AppCenter.
2025-07-06 20:04:47 -07:00
e8e19d5371 Removes release build dependencies on Flipper and SoLoader. 2025-07-06 20:04:47 -07:00
c5242b5206 ViewModel cleanup
Removes unused view models.
Fixes warnings int MonsterViewModel.
2025-07-06 20:04:47 -07:00
ac2e37e494 Cleans up compiler warnings. 2025-07-06 20:04:47 -07:00
c5d857435d Removes unused type converter. 2025-07-06 20:04:47 -07:00
a1fab9d399 Adds functional search using sqlite full text search syntax. 2025-07-06 20:04:46 -07:00
e02e4ec399 Adds Flipper support to debug DB issues. 2025-07-06 20:04:46 -07:00
8706240fb4 Adds DevContent class with dev resources. Specifically an example monster.
Adds a task.
2025-07-06 20:04:46 -07:00
e17c492baf Adds swipe to delete monsters on the library screen. 2025-07-06 20:04:45 -07:00
2df11701e6 Adds floating action button to the library screen to create new monsters.
Adds a snackbar on successful muonster creation to view the new monster.
2025-07-06 20:04:44 -07:00
c6c0e4f758 Makes clicking a monster on the library screen show that monster in the detail screen. 2025-07-06 20:04:44 -07:00
8c233a3bc7 Adds list of monsters to Library screen. 2025-07-06 20:04:44 -07:00
e98b72ad7d Adds MCFragment and makes it the base fragment class. 2025-07-06 20:04:44 -07:00
1fb8dc3a86 Adds MonsterRepository to manage access to the RoomDB store. 2025-07-06 20:04:44 -07:00
8b52b0c3e5 Removes dev navigation on search button clicks. 2025-07-06 20:04:43 -07:00
ee065d7b39 Fixes back button in detail views. 2025-07-06 20:04:43 -07:00
59b319c27d Adds default values to Monster constructor. 2025-07-06 20:04:43 -07:00
cdcb7a60d4 Adds logger. 2025-07-06 20:04:43 -07:00
79106ec9f3 Adds application class. 2025-07-06 20:04:42 -07:00
bb4cbbb98b Upgrades android gradle plugin. 2025-07-06 20:04:41 -07:00
84b0fee261 Migrates Monster class to be storable in roomdb. 2025-07-06 20:04:41 -07:00
0c3ab6dc39 Creates initial app database class and adds minimal DTO support for monsters. 2025-07-06 20:04:37 -07:00
a694205c74 Fixes monster hp calculation so the first hit die is averaged like the others. Characters rolled as players will need to use custom HP. 2025-07-06 20:04:36 -07:00
793987c3fb Adds RoomDB and rxjava3 dependencies. 2025-07-06 20:04:36 -07:00
7d5f9c89a9 Updates build sdk. 2025-07-06 20:04:22 -07:00
6da0bfe70a Code reformat 2025-07-06 20:04:22 -07:00
a44893bca8 Adds actions to monster cards. 2025-07-06 20:04:22 -07:00
67375292a5 Adds abilities to monster cards.
Adds CommonMark dependency and CommonMarkHelper to render it to html.
2025-07-06 20:04:22 -07:00
145c827417 Adds challenge rating to monster cards. 2025-07-06 20:01:01 -07:00
8ff1cb8779 Adds languages to monster cards. 2025-07-06 20:00:59 -07:00
706b58fd2c Adds senses to monster cards. 2025-07-06 20:00:58 -07:00
15973a79f0 Adds condition immunities to monster cards. 2025-07-06 20:00:58 -07:00
94edc44044 Adds damage types to monster cards.
Adds oxfordJoin to StringHelper.
2025-07-06 20:00:58 -07:00
5396b7b014 Adds skills to monster cards. 2025-07-06 20:00:57 -07:00
e0cc8560d1 Adds saving throws to monster card. 2025-07-06 20:00:55 -07:00
c627bb0873 Adds ability scores to monster card. 2025-07-06 20:00:55 -07:00
6d8ec92012 Adds speeds to monster card. 2025-07-06 20:00:55 -07:00
212358e41d Adds hit points to monster card. 2025-07-06 20:00:55 -07:00
5fca394f0e Adds very basic readme. 2025-07-06 20:00:55 -07:00
6c914fb947 Adds armor class and section divider to monster cards. 2025-07-06 20:00:55 -07:00
5a283b8dae Adds monster meta (size, type, subtype/tag, and alignment) to monster cards. 2025-07-06 20:00:54 -07:00
407987e410 Adds monster name to monster cards. 2025-07-06 20:00:50 -07:00
27a1dd7580 Fixes build.gradle to generate safeargs directions for navigation. 2025-07-06 20:00:24 -07:00
775fbf3d9b Fixes deprecation warnings about ViewModelProviders.of(...). 2025-07-06 20:00:24 -07:00
2a75de4bce Adds Monster fragment to view a monster's card.
Adds query box and search button to search fragment.
Makes the search button show a monster card.
2025-07-06 20:00:23 -07:00
72502b3d03 Enables obfuscation and minification for release builds. 2025-07-06 20:00:18 -07:00
2b8a178c05 Adds top level navigation and placeholder fragments. 2025-07-06 20:00:16 -07:00
ab306289bd Sets better color scheme. 2025-07-06 19:59:12 -07:00
74b0c6695f Adds AppCenter config. 2025-07-06 19:58:55 -07:00
44d90ff5ea Adds INTERNET permission to manifest. 2025-07-06 19:58:55 -07:00
5113283550 Initial 2025-07-06 19:58:28 -07:00
119 changed files with 1738 additions and 1712 deletions

1
Android/.gitignore vendored
View File

@@ -8,6 +8,7 @@
/.idea/navEditor.xml /.idea/navEditor.xml
/.idea/assetWizardSettings.xml /.idea/assetWizardSettings.xml
/.idea/dictionaries /.idea/dictionaries
.DS_Store
/build /build
/captures /captures
.externalNativeBuild .externalNativeBuild

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" /> <bytecodeTargetLevel target="1.8" />
</component> </component>
</project> </project>

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
@@ -15,7 +14,6 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@@ -21,10 +21,5 @@
<option name="name" value="Google" /> <option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component> </component>
</project> </project>

19
Android/.idea/misc.xml generated
View File

@@ -1,5 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/dropdown_list_item.xml" value="0.12777777777777777" />
<entry key="app/src/main/res/layout/fragment_dashboard_list_item.xml" value="0.2210144927536232" />
<entry key="app/src/main/res/layout/fragment_edit_strings_list.xml" value="0.2210144927536232" />
<entry key="app/src/main/res/layout/fragment_edit_strings_list_item.xml" value="0.2210144927536232" />
<entry key="app/src/main/res/layout/fragment_edit_traits_list.xml" value="0.2210144927536232" />
<entry key="app/src/main/res/layout/fragment_edit_traits_list_item.xml" value="0.2210144927536232" />
<entry key="app/src/main/res/layout/fragment_search.xml" value="0.16875" />
<entry key="app/src/main/res/layout/simple_list_item.xml" value="0.2210144927536232" />
</map>
</option>
</component>
<component name="NullableNotNullManager"> <component name="NullableNotNullManager">
<option name="myDefaultNullable" value="androidx.annotation.Nullable" /> <option name="myDefaultNullable" value="androidx.annotation.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" /> <option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
@@ -25,7 +39,7 @@
</option> </option>
<option name="myNotNulls"> <option name="myNotNulls">
<value> <value>
<list size="13"> <list size="14">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> <item index="1" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="2" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> <item index="2" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
@@ -39,11 +53,12 @@
<item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" /> <item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
<item index="11" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" /> <item index="11" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
<item index="12" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" /> <item index="12" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
<item index="13" class="java.lang.String" itemvalue="lombok.NonNull" />
</list> </list>
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_X" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -3,7 +3,6 @@
<component name="RunConfigurationProducerService"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />

View File

@@ -9,7 +9,6 @@
<option value="noActionIfCompileErrors" /> <option value="noActionIfCompileErrors" />
<option value="organizeImports" /> <option value="organizeImports" />
<option value="reformat" /> <option value="reformat" />
<option value="rearrange" />
</set> </set>
</option> </option>
<option name="configurationPath" value="" /> <option name="configurationPath" value="" />

4
Android/README.md Normal file
View File

@@ -0,0 +1,4 @@
[![Build status](https://build.appcenter.ms/v0.1/apps/44e4ee45-fe39-4d2d-950f-943e9948ca35/branches/master/badge)](https://appcenter.ms)
# MonsterCards for Android

View File

@@ -21,7 +21,7 @@ def gson_version = '2.8.6'
android { android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.2'
defaultConfig { defaultConfig {
applicationId "com.majinnaibu.monstercards" applicationId "com.majinnaibu.monstercards"
@@ -70,21 +70,21 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
// Google // Google
implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.4.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
// Testing // Testing
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Room DB // Room DB
implementation "io.reactivex.rxjava3:rxjava:$rxjava_version" implementation "io.reactivex.rxjava3:rxjava:$rxjava_version"

View File

@@ -51,9 +51,6 @@
<nav-graph android:value="@navigation/mobile_navigation" /> <nav-graph android:value="@navigation/mobile_navigation" />
</activity> </activity>
<activity
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
android:exported="true" />
</application> </application>
</manifest> </manifest>

View File

@@ -1,14 +1,21 @@
package com.majinnaibu.monstercards; package com.majinnaibu.monstercards;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.room.Database; import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase; import androidx.room.RoomDatabase;
import androidx.room.TypeConverters; import androidx.room.TypeConverters;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.majinnaibu.monstercards.data.MonsterDAO; import com.majinnaibu.monstercards.data.MonsterDAO;
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter; import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter; import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
import com.majinnaibu.monstercards.data.converters.ListOfTraitsConverter; import com.majinnaibu.monstercards.data.converters.ListOfTraitsConverter;
import com.majinnaibu.monstercards.data.converters.SetOfLanguageConverter; import com.majinnaibu.monstercards.data.converters.SetOfLanguageConverter;
import com.majinnaibu.monstercards.data.converters.SetOfSavingThrowConverter;
import com.majinnaibu.monstercards.data.converters.SetOfSkillConverter; import com.majinnaibu.monstercards.data.converters.SetOfSkillConverter;
import com.majinnaibu.monstercards.data.converters.SetOfStringConverter; import com.majinnaibu.monstercards.data.converters.SetOfStringConverter;
import com.majinnaibu.monstercards.data.converters.UUIDConverter; import com.majinnaibu.monstercards.data.converters.UUIDConverter;
@@ -21,10 +28,53 @@ import com.majinnaibu.monstercards.models.MonsterFTS;
ChallengeRatingConverter.class, ChallengeRatingConverter.class,
ListOfTraitsConverter.class, ListOfTraitsConverter.class,
SetOfLanguageConverter.class, SetOfLanguageConverter.class,
SetOfSavingThrowConverter.class,
SetOfSkillConverter.class, SetOfSkillConverter.class,
SetOfStringConverter.class, SetOfStringConverter.class,
UUIDConverter.class, UUIDConverter.class,
}) })
public abstract class AppDatabase extends RoomDatabase { public abstract class AppDatabase extends RoomDatabase {
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// rename table monster to monsters
database.execSQL("ALTER TABLE monster RENAME TO monsters");
// create the fts view
database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `monsters_fts` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)");
// build the initial full text search index
database.execSQL("INSERT INTO monsters_fts(monsters_fts) VALUES('rebuild')");
}
};
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Add the senses column
database.execSQL("ALTER TABLE monsters ADD COLUMN 'senses' TEXT DEFAULT '[]'");
database.execSQL("CREATE TABLE new_monsters (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))");
database.execSQL("INSERT INTO new_monsters(id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions) SELECT id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions FROM monsters");
database.execSQL("DROP TABLE monsters");
database.execSQL("ALTER TABLE new_monsters RENAME TO monsters");
}
};
private static AppDatabase mDB = null;
public static AppDatabase getInstance(Context context) {
if (mDB == null) {
synchronized (AppDatabase.class) {
if (mDB == null) {
// .fallbackToDestructiveMigration()
mDB = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "monsters")
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.fallbackToDestructiveMigrationOnDowngrade()
// .fallbackToDestructiveMigration()
.build();
}
}
}
return mDB;
}
public abstract MonsterDAO monsterDAO(); public abstract MonsterDAO monsterDAO();
} }

View File

@@ -6,7 +6,6 @@ import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
@@ -55,7 +54,9 @@ public class MainActivity extends AppCompatActivity {
.build(); .build();
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController(); NavController navController = navHostFragment.getNavController();
navController.addOnDestinationChangedListener(FlipperInitializer::sendNavigationEvent); navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
FlipperInitializer.sendNavigationEvent(controller, destination, arguments);
});
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView, navController); NavigationUI.setupWithNavController(navView, navController);
onNewIntent(getIntent()); onNewIntent(getIntent());
@@ -67,15 +68,14 @@ public class MainActivity extends AppCompatActivity {
String json = readMonsterJSONFromIntent(intent); String json = readMonsterJSONFromIntent(intent);
if (!StringHelper.isNullOrEmpty(json)) { if (!StringHelper.isNullOrEmpty(json)) {
NavHostFragment navHostFragment = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment)); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController(); NavController navController = navHostFragment.getNavController();
NavDirections action = MobileNavigationDirections.actionGlobalMonsterImportFragment(json); NavDirections action = MobileNavigationDirections.actionGlobalMonsterImportFragment(json);
navController.navigate(action); navController.navigate(action);
} }
} }
@Nullable private String readMonsterJSONFromIntent(Intent intent) {
private String readMonsterJSONFromIntent(@NonNull Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
String type = intent.getType(); String type = intent.getType();
@@ -86,7 +86,7 @@ public class MainActivity extends AppCompatActivity {
} else if ("android.intent.action.VIEW".equals(action) && ("text/plain".equals(type) || "application/octet-stream".equals(type))) { } else if ("android.intent.action.VIEW".equals(action) && ("text/plain".equals(type) || "application/octet-stream".equals(type))) {
uri = intent.getData(); uri = intent.getData();
} else { } else {
Logger.logError(String.format("unexpected launch configuration action: %s, type: %s", action, type)); Logger.logError(String.format("unexpected launch configuration action: %s, type: %s, uri: %s", action, type, uri));
} }
if (uri == null) { if (uri == null) {
return null; return null;
@@ -98,7 +98,6 @@ public class MainActivity extends AppCompatActivity {
return json; return json;
} }
@Nullable
private String readContentsOfUri(Uri uri) { private String readContentsOfUri(Uri uri) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
try (InputStream inputStream = try (InputStream inputStream =

View File

@@ -3,39 +3,12 @@ package com.majinnaibu.monstercards;
import android.app.Application; import android.app.Application;
import android.content.res.Configuration; import android.content.res.Configuration;
import androidx.annotation.NonNull;
import androidx.room.Room;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
import com.majinnaibu.monstercards.data.MonsterRepository; import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.init.FlipperInitializer; import com.majinnaibu.monstercards.init.FlipperInitializer;
public class MonsterCardsApplication extends Application { public class MonsterCardsApplication extends Application {
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// rename table monster to monsters
database.execSQL("ALTER TABLE monster RENAME TO monsters");
// create the fts view
database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `monsters_fts` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)");
// build the initial full text search index
database.execSQL("INSERT INTO monsters_fts(monsters_fts) VALUES('rebuild')");
}
};
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// Add the senses column
database.execSQL("ALTER TABLE monsters ADD COLUMN 'senses' TEXT DEFAULT '[]'");
database.execSQL("CREATE TABLE new_monsters (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))");
database.execSQL("INSERT INTO new_monsters(id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions) SELECT id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions FROM monsters");
database.execSQL("DROP TABLE monsters");
database.execSQL("ALTER TABLE new_monsters RENAME TO monsters");
}
};
private MonsterRepository m_monsterLibraryRepository; private MonsterRepository m_monsterLibraryRepository;
@@ -54,15 +27,8 @@ public class MonsterCardsApplication extends Application {
// Required initialization logic here! // Required initialization logic here!
FlipperInitializer.init(this); FlipperInitializer.init(this);
AppDatabase mDB = AppDatabase.getInstance(this);
// .fallbackToDestructiveMigration() m_monsterLibraryRepository = new MonsterRepository(mDB);
AppDatabase m_db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "monsters")
.addMigrations(MIGRATION_1_2)
.addMigrations(MIGRATION_2_3)
.fallbackToDestructiveMigrationOnDowngrade()
// .fallbackToDestructiveMigration()
.build();
m_monsterLibraryRepository = new MonsterRepository(m_db);
} }
// Called by the system when the device configuration changes while your component is running. // Called by the system when the device configuration changes while your component is running.

View File

@@ -1,7 +1,5 @@
package com.majinnaibu.monstercards.data; package com.majinnaibu.monstercards.data;
import androidx.annotation.NonNull;
import com.majinnaibu.monstercards.data.enums.AbilityScore; import com.majinnaibu.monstercards.data.enums.AbilityScore;
import com.majinnaibu.monstercards.data.enums.AdvantageType; import com.majinnaibu.monstercards.data.enums.AdvantageType;
import com.majinnaibu.monstercards.data.enums.ArmorType; import com.majinnaibu.monstercards.data.enums.ArmorType;
@@ -14,7 +12,6 @@ import com.majinnaibu.monstercards.models.Trait;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class DevContent { public final class DevContent {
@NonNull
public static Monster createSampleMonster() { public static Monster createSampleMonster() {
Monster monster = new Monster(); Monster monster = new Monster();
// Name // Name

View File

@@ -3,10 +3,8 @@ package com.majinnaibu.monstercards.data;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.majinnaibu.monstercards.AppDatabase; import com.majinnaibu.monstercards.AppDatabase;
import com.majinnaibu.monstercards.helpers.StringHelper;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -15,16 +13,16 @@ import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
@SuppressWarnings("ResultOfMethodCallIgnored")
public class MonsterRepository { public class MonsterRepository {
private final AppDatabase m_db; private AppDatabase m_db;
public MonsterRepository(@NonNull AppDatabase db) { public MonsterRepository(@NonNull AppDatabase db) {
m_db = db; m_db = db;
} }
public Flowable<List<Monster>> getMonsters() { public Flowable<List<Monster>> getMonsters() {
return m_db.monsterDAO() return m_db.monsterDAO()
.getAll() .getAll()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -33,21 +31,12 @@ public class MonsterRepository {
public Flowable<List<Monster>> searchMonsters(String searchText) { public Flowable<List<Monster>> searchMonsters(String searchText) {
return m_db.monsterDAO() return m_db.monsterDAO()
.getAll() .search(searchText)
.map(monsters -> {
ArrayList<Monster> filteredMonsters = new ArrayList<>();
for (Monster monster : monsters) {
if (Helpers.monsterMatchesSearch(monster, searchText)) {
filteredMonsters.add(monster);
}
}
return (List<Monster>) filteredMonsters;
})
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()); .observeOn(AndroidSchedulers.mainThread());
} }
public Flowable<Monster> getMonster(@NonNull UUID monsterId) { public Flowable<Monster> getMonster(UUID monsterId) {
return m_db.monsterDAO() return m_db.monsterDAO()
.loadAllByIds(new String[]{monsterId.toString()}) .loadAllByIds(new String[]{monsterId.toString()})
.map( .map(
@@ -79,34 +68,4 @@ public class MonsterRepository {
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()); result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
return result; return result;
} }
private static class Helpers {
static boolean monsterMatchesSearch(Monster monster, String searchText) {
if (StringHelper.isNullOrEmpty(searchText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.name, searchText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.size, searchText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.type, searchText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.subtype, searchText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.alignment, searchText)) {
return true;
}
return false;
}
}
} }

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.data.converters; package com.majinnaibu.monstercards.data.converters;
import androidx.annotation.NonNull;
import androidx.room.TypeConverter; import androidx.room.TypeConverter;
import com.majinnaibu.monstercards.data.enums.ArmorType; import com.majinnaibu.monstercards.data.enums.ArmorType;
@@ -8,7 +7,7 @@ import com.majinnaibu.monstercards.data.enums.ArmorType;
public class ArmorTypeConverter { public class ArmorTypeConverter {
@TypeConverter @TypeConverter
public static String fromArmorType(@NonNull ArmorType armorType) { public static String fromArmorType(ArmorType armorType) {
return armorType.stringValue; return armorType.stringValue;
} }

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.data.converters; package com.majinnaibu.monstercards.data.converters;
import androidx.annotation.NonNull;
import androidx.room.TypeConverter; import androidx.room.TypeConverter;
import com.majinnaibu.monstercards.data.enums.ChallengeRating; import com.majinnaibu.monstercards.data.enums.ChallengeRating;
@@ -8,7 +7,7 @@ import com.majinnaibu.monstercards.data.enums.ChallengeRating;
public class ChallengeRatingConverter { public class ChallengeRatingConverter {
@TypeConverter @TypeConverter
public static String fromChallengeRating(@NonNull ChallengeRating challengeRating) { public static String fromChallengeRating(ChallengeRating challengeRating) {
return challengeRating.stringValue; return challengeRating.stringValue;
} }

View File

@@ -0,0 +1,31 @@
package com.majinnaibu.monstercards.data.converters;
import androidx.room.TypeConverter;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.majinnaibu.monstercards.models.SavingThrow;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
public class SetOfSavingThrowConverter {
@TypeConverter
public static String fromSetOfSavingThrow(Set<SavingThrow> savingThrows) {
Gson gson = new Gson();
SavingThrow[] saves = new SavingThrow[savingThrows.size()];
savingThrows.toArray(saves);
return gson.toJson(saves);
}
@TypeConverter
public static Set<SavingThrow> setOfSavingThrowFromString(String string) {
Gson gson = new Gson();
Type setType = new TypeToken<HashSet<SavingThrow>>() {
}.getType();
return gson.fromJson(string, setType);
}
}

View File

@@ -1,15 +1,13 @@
package com.majinnaibu.monstercards.data.converters; package com.majinnaibu.monstercards.data.converters;
import androidx.annotation.NonNull;
import androidx.room.TypeConverter; import androidx.room.TypeConverter;
import java.util.UUID; import java.util.UUID;
public class UUIDConverter { public class UUIDConverter {
@NonNull
@TypeConverter @TypeConverter
public static String fromUUID(@NonNull UUID uuid) { public static String fromUUID(UUID uuid) {
return uuid.toString(); return uuid.toString();
} }

View File

@@ -5,7 +5,7 @@ public enum AbilityScore {
STRENGTH("strength", "Strength", "STR"), STRENGTH("strength", "Strength", "STR"),
DEXTERITY("dexterity", "Dexterity", "DEX"), DEXTERITY("dexterity", "Dexterity", "DEX"),
CONSTITUTION("constitution", "Constitution", "CON"), CONSTITUTION("constitution", "Constitution", "CON"),
INTELLIGENCE("intelligence", "Intelligence", "INT"), INTELLIGENCE("intellligence", "Intelligence", "INT"),
WISDOM("wisdom", "Wisdom", "WIS"), WISDOM("wisdom", "Wisdom", "WIS"),
CHARISMA("charisma", "Charisma", "CHA"), CHARISMA("charisma", "Charisma", "CHA"),
; ;

View File

@@ -3,7 +3,7 @@ package com.majinnaibu.monstercards.data.enums;
public enum ProficiencyType { public enum ProficiencyType {
NONE("none", "None", ""), NONE("none", "None", ""),
PROFICIENT("proficient", "Proficient", "P"), PROFICIENT("proficient", "Proficient", "P"),
EXPERTISE("expertise", "Expertise", "Ex"), EXPERTISE("experties", "Expertise", "Ex"),
; ;
public final String displayName; public final String displayName;

View File

@@ -1,11 +1,9 @@
package com.majinnaibu.monstercards.helpers; package com.majinnaibu.monstercards.helpers;
import androidx.annotation.NonNull;
import java.util.Objects; import java.util.Objects;
public final class ArrayHelper { public final class ArrayHelper {
public static int indexOf(@NonNull Object[] array, Object target) { public static int indexOf(Object[] array, Object target) {
for (int index = 0; index < array.length; index++) { for (int index = 0; index < array.length; index++) {
if (Objects.equals(array[index], target)) { if (Objects.equals(array[index], target)) {
return index; return index;

View File

@@ -4,9 +4,20 @@ import org.commonmark.node.Document;
import org.commonmark.node.Node; import org.commonmark.node.Node;
import org.commonmark.node.Paragraph; import org.commonmark.node.Paragraph;
import org.commonmark.parser.Parser; import org.commonmark.parser.Parser;
import org.commonmark.renderer.NodeRenderer;
import org.commonmark.renderer.html.HtmlNodeRendererContext;
import org.commonmark.renderer.html.HtmlNodeRendererFactory;
import org.commonmark.renderer.html.HtmlRenderer; import org.commonmark.renderer.html.HtmlRenderer;
public final class CommonMarkHelper { public final class CommonMarkHelper {
private static final class MyNodeRendererFactory implements HtmlNodeRendererFactory {
@Override
public NodeRenderer create(HtmlNodeRendererContext context) {
return null;
}
}
public static String toHtml(String rawCommonMark) { public static String toHtml(String rawCommonMark) {
Parser parser = Parser.builder().build(); Parser parser = Parser.builder().build();
Node document = parser.parse(rawCommonMark); Node document = parser.parse(rawCommonMark);

View File

@@ -1,7 +1,5 @@
package com.majinnaibu.monstercards.helpers; package com.majinnaibu.monstercards.helpers;
import androidx.annotation.NonNull;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@@ -17,16 +15,17 @@ import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.Skill; import com.majinnaibu.monstercards.models.Skill;
import com.majinnaibu.monstercards.models.Trait; import com.majinnaibu.monstercards.models.Trait;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
public class MonsterImportHelper { public class MonsterImportHelper {
@NonNull
public static Monster fromJSON(String json) { public static Monster fromJSON(String json) {
JsonObject rootDict = JsonParser.parseString(json).getAsJsonObject(); JsonParser parser = new JsonParser();
JsonObject rootDict = parser.parse(json).getAsJsonObject();
Monster monster = new Monster(); Monster monster = new Monster();
monster.name = Helpers.getString(rootDict, "name"); monster.name = Helpers.getString(rootDict, "name");
@@ -111,7 +110,7 @@ public class MonsterImportHelper {
return getString(dict, name, ""); return getString(dict, name, "");
} }
public static String getString(@NonNull JsonObject dict, String name, String defaultValue) { public static String getString(@NotNull JsonObject dict, String name, String defaultValue) {
if (dict.has(name)) { if (dict.has(name)) {
return dict.get(name).getAsString(); return dict.get(name).getAsString();
} }
@@ -123,19 +122,13 @@ public class MonsterImportHelper {
return getInt(dict, name, 0); return getInt(dict, name, 0);
} }
public static int getInt(@NonNull JsonObject dict, String name, int defaultValue) { public static int getInt(@NotNull JsonObject dict, String name, int defaultValue) {
if (dict.has(name)) { if (dict.has(name)) {
JsonElement element = dict.get(name); JsonElement element = dict.get(name);
if (element.isJsonPrimitive()) { if (element.isJsonPrimitive()) {
JsonPrimitive rawValue = element.getAsJsonPrimitive(); JsonPrimitive rawValue = element.getAsJsonPrimitive();//dict.getAsJsonPrimitive(name);
if (rawValue.isNumber()) { if (rawValue.isNumber()) {
return rawValue.getAsInt(); return rawValue.getAsInt();
} else {
try {
return rawValue.getAsInt();
} catch (Exception ex) {
return defaultValue;
}
} }
} }
} }
@@ -146,29 +139,22 @@ public class MonsterImportHelper {
return getBool(dict, name, false); return getBool(dict, name, false);
} }
public static boolean getBool(@NonNull JsonObject dict, String name, boolean defaultValue) { public static boolean getBool(@NotNull JsonObject dict, String name, boolean defaultValue) {
if (dict.has(name)) { if (dict.has(name)) {
JsonElement element = dict.get(name); JsonElement element = dict.get(name);
if (element.isJsonPrimitive()) { if (element.isJsonPrimitive()) {
JsonPrimitive rawValue = element.getAsJsonPrimitive(); JsonPrimitive rawValue = element.getAsJsonPrimitive();
if (rawValue.isBoolean()) { if (rawValue.isBoolean()) {
return rawValue.getAsBoolean(); return rawValue.getAsBoolean();
} else {
try {
return rawValue.getAsBoolean();
} catch (Exception ex) {
return defaultValue;
}
} }
} }
} }
return defaultValue; return defaultValue;
} }
@NonNull @NotNull
public static String formatDistance(String name, int distance) { public static String formatDistance(String name, int distance) {
// TODO: consider moving this to a string resource so it can be localized return String.format("%s %d ft.", name, distance);
return String.format(Locale.getDefault(), "%s %d ft.", name, distance);
} }
public static void addSense(Monster monster, JsonObject root, String name) { public static void addSense(Monster monster, JsonObject root, String name) {
@@ -178,8 +164,8 @@ public class MonsterImportHelper {
} }
} }
@NonNull @NotNull
public static List<Trait> getListOfTraits(@NonNull JsonObject dict, String name) { public static List<Trait> getListOfTraits(@NotNull JsonObject dict, String name) {
ArrayList<Trait> traits = new ArrayList<>(); ArrayList<Trait> traits = new ArrayList<>();
if (dict.has(name)) { if (dict.has(name)) {
JsonElement arrayElement = dict.get(name); JsonElement arrayElement = dict.get(name);
@@ -191,7 +177,7 @@ public class MonsterImportHelper {
if (jsonElement.isJsonObject()) { if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject(); JsonObject jsonObject = jsonElement.getAsJsonObject();
String traitName = Helpers.getString(jsonObject, "name"); String traitName = Helpers.getString(jsonObject, "name");
String description = Helpers.getString(jsonObject, "desc"); String description = Helpers.getString(jsonObject, "description");
Trait trait = new Trait(traitName, description); Trait trait = new Trait(traitName, description);
traits.add(trait); traits.add(trait);
} }
@@ -201,7 +187,7 @@ public class MonsterImportHelper {
return traits; return traits;
} }
public static void addSavingThrows(Monster monster, @NonNull JsonObject root) { public static void addSavingThrows(Monster monster, JsonObject root) {
if (root.has("sthrows")) { if (root.has("sthrows")) {
JsonElement arrayElement = root.get("sthrows"); JsonElement arrayElement = root.get("sthrows");
if (arrayElement.isJsonArray()) { if (arrayElement.isJsonArray()) {
@@ -231,8 +217,7 @@ public class MonsterImportHelper {
} }
} }
@NonNull public static Set<Skill> getSetOfSkills(JsonObject root) {
public static Set<Skill> getSetOfSkills(@NonNull JsonObject root) {
HashSet<Skill> skills = new HashSet<>(); HashSet<Skill> skills = new HashSet<>();
if (root.has("skills")) { if (root.has("skills")) {
JsonElement arrayElement = root.get("skills"); JsonElement arrayElement = root.get("skills");
@@ -256,13 +241,11 @@ public class MonsterImportHelper {
return skills; return skills;
} }
@NonNull
public static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) { public static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) {
return getSetOfDamageTypes(rootDict, name, null); return getSetOfDamageTypes(rootDict, name, null);
} }
@NonNull public static Set<String> getSetOfDamageTypes(JsonObject root, String name, String type) {
public static Set<String> getSetOfDamageTypes(@NonNull JsonObject root, String name, String type) {
HashSet<String> damageTypes = new HashSet<>(); HashSet<String> damageTypes = new HashSet<>();
if (root.has(name)) { if (root.has(name)) {
JsonElement arrayElement = root.get(name); JsonElement arrayElement = root.get(name);
@@ -285,8 +268,7 @@ public class MonsterImportHelper {
return damageTypes; return damageTypes;
} }
@NonNull public static Set<Language> getSetOfLanguages(JsonObject root, String name) {
public static Set<Language> getSetOfLanguages(@NonNull JsonObject root, String name) {
HashSet<Language> languages = new HashSet<>(); HashSet<Language> languages = new HashSet<>();
if (root.has(name)) { if (root.has(name)) {
JsonElement arrayElement = root.get(name); JsonElement arrayElement = root.get(name);

View File

@@ -1,11 +1,6 @@
package com.majinnaibu.monstercards.helpers; package com.majinnaibu.monstercards.helpers;
import androidx.annotation.NonNull; @SuppressWarnings({"BooleanMethodIsAlwaysInverted", "RedundantIfStatement"})
import androidx.annotation.Nullable;
import java.util.Collection;
@SuppressWarnings({"RedundantIfStatement"})
public final class StringHelper { public final class StringHelper {
public static boolean isNullOrEmpty(CharSequence value) { public static boolean isNullOrEmpty(CharSequence value) {
if (value == null) { if (value == null) {
@@ -18,62 +13,4 @@ public final class StringHelper {
return false; return false;
} }
@NonNull
public static String join(String delimiter, @NonNull Collection<String> strings) {
int length = strings.size();
if (length < 1) {
return "";
} else {
StringBuilder sb = new StringBuilder();
boolean isFirst = true;
for (String element : strings) {
if (!isFirst) {
sb.append(delimiter);
}
sb.append(element);
isFirst = false;
}
return sb.toString();
}
}
public static String oxfordJoin(String delimiter, String lastDelimiter, String onlyDelimiter, @NonNull Collection<String> strings) {
int length = strings.size();
if (length < 1) {
return "";
} else if (length == 2) {
return join(onlyDelimiter, strings);
} else {
StringBuilder sb = new StringBuilder();
int index = 0;
int lastIndex = length - 1;
for (String element : strings) {
if (index > 0 && index < lastIndex) {
sb.append(delimiter);
} else if (index > 0 && index >= lastIndex) {
sb.append(lastDelimiter);
}
sb.append(element);
index++;
}
return sb.toString();
}
}
@Nullable
public static Integer parseInt(String s) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException _ex) {
return null;
}
}
public static boolean containsCaseInsensitive(@NonNull String text, @NonNull String search) {
// TODO: find a locale independent way to do this
return text.toLowerCase().contains(search.toLowerCase());
}
} }

View File

@@ -1,74 +1,37 @@
package com.majinnaibu.monstercards.models; package com.majinnaibu.monstercards.models;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects;
public class Language implements Comparator<Language>, Comparable<Language> { public class Language implements Comparator<Language>, Comparable<Language> {
private String mName;
private boolean mSpeaks;
public Language(String name, boolean speaks) { public Language(String name, boolean speaks) {
mName = name; mName = name;
mSpeaks = speaks; mSpeaks = speaks;
} }
private String mName;
public String getName() { public String getName() {
return mName; return mName;
} }
public void setName(String value) { public void setName(String value) {
mName = value; mName = value;
} }
private boolean mSpeaks;
public boolean getSpeaks() { public boolean getSpeaks() {
return mSpeaks; return mSpeaks;
} }
public void setSpeaks(boolean value) { public void setSpeaks(boolean value) {
mSpeaks = value; mSpeaks = value;
} }
@Override @Override
public int compareTo(Language o) { public int compareTo(Language o) {
if (this.mSpeaks && !o.mSpeaks) { return this.getName().compareToIgnoreCase(o.getName());
return -1;
}
if (!this.mSpeaks && o.mSpeaks) {
return 1;
}
return this.mName.compareToIgnoreCase(o.mName);
} }
@Override @Override
public int compare(@NonNull Language o1, Language o2) { public int compare(Language o1, Language o2) {
if (o1.mSpeaks && !o2.mSpeaks) { return o1.getName().compareToIgnoreCase(o2.getName());
return -1;
}
if (!o1.mSpeaks && o2.mSpeaks) {
return 1;
}
return o1.mName.compareToIgnoreCase(o2.mName);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof Language)) {
return false;
}
Language otherLanguage = (Language) obj;
if (!Objects.equals(this.mName, otherLanguage.mName)) {
return false;
}
if (this.mSpeaks != otherLanguage.mSpeaks) {
return false;
}
return true;
} }
} }

View File

@@ -16,11 +16,14 @@ import com.majinnaibu.monstercards.data.enums.ProficiencyType;
import com.majinnaibu.monstercards.helpers.StringHelper; import com.majinnaibu.monstercards.helpers.StringHelper;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@@ -268,6 +271,69 @@ public class Monster {
regionalActions = new ArrayList<>(); regionalActions = new ArrayList<>();
} }
public static boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return Objects.equals(oldItem.id, newItem.id);
}
public static boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return Objects.equals(oldItem.abilities, newItem.abilities) &&
Objects.equals(oldItem.actions, newItem.actions) &&
Objects.equals(oldItem.alignment, newItem.alignment) &&
Objects.equals(oldItem.armorType, newItem.armorType) &&
Objects.equals(oldItem.burrowSpeed, newItem.burrowSpeed) &&
Objects.equals(oldItem.canHover, newItem.canHover) &&
Objects.equals(oldItem.challengeRating, newItem.challengeRating) &&
Objects.equals(oldItem.charismaSavingThrowAdvantage, newItem.charismaSavingThrowAdvantage) &&
Objects.equals(oldItem.charismaSavingThrowProficiency, newItem.charismaSavingThrowProficiency) &&
Objects.equals(oldItem.charismaScore, newItem.charismaScore) &&
Objects.equals(oldItem.climbSpeed, newItem.climbSpeed) &&
Objects.equals(oldItem.conditionImmunities, newItem.conditionImmunities) &&
Objects.equals(oldItem.constitutionSavingThrowAdvantage, newItem.constitutionSavingThrowAdvantage) &&
Objects.equals(oldItem.constitutionSavingThrowProficiency, newItem.constitutionSavingThrowProficiency) &&
Objects.equals(oldItem.constitutionScore, newItem.constitutionScore) &&
Objects.equals(oldItem.customChallengeRatingDescription, newItem.customChallengeRatingDescription) &&
Objects.equals(oldItem.customHPDescription, newItem.customHPDescription) &&
Objects.equals(oldItem.customProficiencyBonus, newItem.customProficiencyBonus) &&
Objects.equals(oldItem.customSpeedDescription, newItem.customSpeedDescription) &&
Objects.equals(oldItem.damageImmunities, newItem.damageImmunities) &&
Objects.equals(oldItem.damageResistances, newItem.damageResistances) &&
Objects.equals(oldItem.damageVulnerabilities, newItem.damageVulnerabilities) &&
Objects.equals(oldItem.dexteritySavingThrowAdvantage, newItem.dexteritySavingThrowAdvantage) &&
Objects.equals(oldItem.dexteritySavingThrowProficiency, newItem.dexteritySavingThrowProficiency) &&
Objects.equals(oldItem.dexterityScore, newItem.dexterityScore) &&
Objects.equals(oldItem.flySpeed, newItem.flySpeed) &&
Objects.equals(oldItem.hasCustomHP, newItem.hasCustomHP) &&
Objects.equals(oldItem.hasCustomSpeed, newItem.hasCustomSpeed) &&
Objects.equals(oldItem.hitDice, newItem.hitDice) &&
Objects.equals(oldItem.intelligenceSavingThrowAdvantage, newItem.intelligenceSavingThrowAdvantage) &&
Objects.equals(oldItem.intelligenceSavingThrowProficiency, newItem.intelligenceSavingThrowProficiency) &&
Objects.equals(oldItem.intelligenceScore, newItem.intelligenceScore) &&
Objects.equals(oldItem.lairActions, newItem.lairActions) &&
Objects.equals(oldItem.languages, newItem.languages) &&
Objects.equals(oldItem.legendaryActions, newItem.legendaryActions) &&
Objects.equals(oldItem.name, newItem.name) &&
Objects.equals(oldItem.naturalArmorBonus, newItem.naturalArmorBonus) &&
Objects.equals(oldItem.otherArmorDescription, newItem.otherArmorDescription) &&
Objects.equals(oldItem.reactions, newItem.reactions) &&
Objects.equals(oldItem.regionalActions, newItem.regionalActions) &&
Objects.equals(oldItem.senses, newItem.senses) &&
Objects.equals(oldItem.shieldBonus, newItem.shieldBonus) &&
Objects.equals(oldItem.size, newItem.size) &&
Objects.equals(oldItem.skills, newItem.skills) &&
Objects.equals(oldItem.strengthSavingThrowAdvantage, newItem.strengthSavingThrowAdvantage) &&
Objects.equals(oldItem.strengthSavingThrowProficiency, newItem.strengthSavingThrowProficiency) &&
Objects.equals(oldItem.strengthScore, newItem.strengthScore) &&
Objects.equals(oldItem.subtype, newItem.subtype) &&
Objects.equals(oldItem.swimSpeed, newItem.swimSpeed) &&
Objects.equals(oldItem.telepathyRange, newItem.telepathyRange) &&
Objects.equals(oldItem.type, newItem.type) &&
Objects.equals(oldItem.understandsButDescription, newItem.understandsButDescription) &&
Objects.equals(oldItem.wisdomSavingThrowAdvantage, newItem.wisdomSavingThrowAdvantage) &&
Objects.equals(oldItem.wisdomSavingThrowProficiency, newItem.wisdomSavingThrowProficiency) &&
Objects.equals(oldItem.wisdomScore, newItem.wisdomScore) &&
Objects.equals(oldItem.walkSpeed, newItem.walkSpeed);
}
public String getMeta() { public String getMeta() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
boolean isFirstOutput = true; boolean isFirstOutput = true;
@@ -304,7 +370,7 @@ public class Monster {
return sb.toString(); return sb.toString();
} }
public int getAbilityScore(@NonNull AbilityScore abilityScore) { public int getAbilityScore(@NotNull AbilityScore abilityScore) {
switch (abilityScore) { switch (abilityScore) {
case STRENGTH: case STRENGTH:
return strengthScore; return strengthScore;
@@ -323,7 +389,7 @@ public class Monster {
} }
} }
public int getAbilityModifier(@NonNull AbilityScore abilityScore) { public int getAbilityModifier(@NotNull AbilityScore abilityScore) {
switch (abilityScore) { switch (abilityScore) {
case STRENGTH: case STRENGTH:
return getStrengthModifier(); return getStrengthModifier();
@@ -342,7 +408,7 @@ public class Monster {
} }
} }
public AdvantageType getSavingThrowAdvantageType(@NonNull AbilityScore abilityScore) { public AdvantageType getSavingThrowAdvantageType(@NotNull AbilityScore abilityScore) {
switch (abilityScore) { switch (abilityScore) {
case STRENGTH: case STRENGTH:
return strengthSavingThrowAdvantage; return strengthSavingThrowAdvantage;
@@ -361,7 +427,7 @@ public class Monster {
} }
} }
public ProficiencyType getSavingThrowProficiencyType(@NonNull AbilityScore abilityScore) { public ProficiencyType getSavingThrowProficiencyType(@NotNull AbilityScore abilityScore) {
switch (abilityScore) { switch (abilityScore) {
case STRENGTH: case STRENGTH:
return strengthSavingThrowProficiency; return strengthSavingThrowProficiency;
@@ -607,7 +673,7 @@ public class Monster {
} }
} }
public int getProficiencyBonus(@NonNull ProficiencyType proficiencyType) { public int getProficiencyBonus(@NotNull ProficiencyType proficiencyType) {
switch (proficiencyType) { switch (proficiencyType) {
case PROFICIENT: case PROFICIENT:
return getProficiencyBonus(); return getProficiencyBonus();
@@ -751,7 +817,7 @@ public class Monster {
return abilityDescriptions; return abilityDescriptions;
} }
public String getPlaceholderReplacedText(@NonNull String rawText) { public String getPlaceholderReplacedText(@NotNull String rawText) {
return rawText return rawText
.replaceAll("\\[STR SAVE]", String.format("%+d", getSpellSaveDC(AbilityScore.STRENGTH))) .replaceAll("\\[STR SAVE]", String.format("%+d", getSpellSaveDC(AbilityScore.STRENGTH)))
.replaceAll("\\[STR ATK]", String.format("%+d", getAttackBonus(AbilityScore.STRENGTH))) .replaceAll("\\[STR ATK]", String.format("%+d", getAttackBonus(AbilityScore.STRENGTH)))
@@ -816,7 +882,7 @@ public class Monster {
} }
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable @org.jetbrains.annotations.Nullable Object obj) {
if (obj == null) { if (obj == null) {
return false; return false;
} }

View File

@@ -2,14 +2,11 @@ package com.majinnaibu.monstercards.models;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import androidx.annotation.Nullable;
import com.majinnaibu.monstercards.data.enums.AbilityScore; import com.majinnaibu.monstercards.data.enums.AbilityScore;
import com.majinnaibu.monstercards.data.enums.AdvantageType; import com.majinnaibu.monstercards.data.enums.AdvantageType;
import com.majinnaibu.monstercards.data.enums.ProficiencyType; import com.majinnaibu.monstercards.data.enums.ProficiencyType;
import java.util.Comparator; import java.util.Comparator;
import java.util.Objects;
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
public class Skill implements Comparator<Skill>, Comparable<Skill> { public class Skill implements Comparator<Skill>, Comparable<Skill> {
@@ -52,7 +49,7 @@ public class Skill implements Comparator<Skill>, Comparable<Skill> {
return String.format( return String.format(
"%s%s %+d%s", "%s%s %+d%s",
name.charAt(0), name.substring(0, 1),
name.substring(1), name.substring(1),
bonus, bonus,
advantageType == AdvantageType.ADVANTAGE ? " A" : advantageType == AdvantageType.DISADVANTAGE ? " D" : "" advantageType == AdvantageType.ADVANTAGE ? " A" : advantageType == AdvantageType.DISADVANTAGE ? " D" : ""
@@ -69,27 +66,4 @@ public class Skill implements Comparator<Skill>, Comparable<Skill> {
return o1.name.compareToIgnoreCase(o2.name); return o1.name.compareToIgnoreCase(o2.name);
} }
@Override
public boolean equals(@Nullable Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof Skill)) {
return false;
}
Skill otherSkill = (Skill) obj;
if (!Objects.equals(this.name, otherSkill.name)) {
return false;
}
if (this.abilityScore != otherSkill.abilityScore) {
return false;
}
if (this.advantageType != otherSkill.advantageType) {
return false;
}
if (this.proficiencyType != otherSkill.proficiencyType) {
return false;
}
return true;
}
} }

View File

@@ -1,11 +1,6 @@
package com.majinnaibu.monstercards.models; package com.majinnaibu.monstercards.models;
import androidx.annotation.Nullable; public class Trait {
import java.util.Comparator;
import java.util.Objects;
public class Trait implements Comparator<Trait>, Comparable<Trait> {
public String name; public String name;
public String description; public String description;
@@ -14,36 +9,4 @@ public class Trait implements Comparator<Trait>, Comparable<Trait> {
this.name = name; this.name = name;
this.description = description; this.description = description;
} }
@Override
public int compareTo(Trait o) {
return compare(this, o);
}
@Override
public int compare(Trait o1, Trait o2) {
int result = o1.name.compareToIgnoreCase(o2.name);
if (result != 0) {
return result;
}
return o1.description.compareToIgnoreCase(o2.description);
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof Trait)) {
return false;
}
Trait otherTrait = (Trait) obj;
if (!Objects.equals(this.name, otherTrait.name)) {
return false;
}
if (!Objects.equals(this.description, otherTrait.description)) {
return false;
}
return true;
}
} }

View File

@@ -0,0 +1,72 @@
package com.majinnaibu.monstercards.placeholder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class for providing sample content for user interfaces created by
* Android template wizards.
* <p>
* TODO: Replace all uses of this class before publishing your app.
*/
public class PlaceholderContent {
/**
* An array of sample (placeholder) items.
*/
public static final List<PlaceholderItem> ITEMS = new ArrayList<PlaceholderItem>();
/**
* A map of sample (placeholder) items, by ID.
*/
public static final Map<String, PlaceholderItem> ITEM_MAP = new HashMap<String, PlaceholderItem>();
private static final int COUNT = 25;
static {
// Add some sample items.
for (int i = 1; i <= COUNT; i++) {
addItem(createPlaceholderItem(i));
}
}
private static void addItem(PlaceholderItem item) {
ITEMS.add(item);
ITEM_MAP.put(item.id, item);
}
private static PlaceholderItem createPlaceholderItem(int position) {
return new PlaceholderItem(String.valueOf(position), "Item " + position, makeDetails(position));
}
private static String makeDetails(int position) {
StringBuilder builder = new StringBuilder();
builder.append("Details about Item: ").append(position);
for (int i = 0; i < position; i++) {
builder.append("\nMore details information here.");
}
return builder.toString();
}
/**
* A placeholder item representing a piece of content.
*/
public static class PlaceholderItem {
public final String id;
public final String content;
public final String details;
public PlaceholderItem(String id, String content, String details) {
this.id = id;
this.content = content;
this.details = details;
}
@Override
public String toString() {
return content;
}
}
}

View File

@@ -0,0 +1,119 @@
package com.majinnaibu.monstercards.ui;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.library.LibraryFragment;
import com.majinnaibu.monstercards.ui.library.LibraryFragmentDirections;
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.schedulers.Schedulers;
public class MonsterListRecyclerViewAdapter extends RecyclerView.Adapter<MonsterListRecyclerViewAdapter.ViewHolder> {
public interface ItemCallback {
void onItem(Monster monster);
}
// TODO: Replace SimpleItemRecyclerViewAdapter with something better like MonsterListRecyclerViewAdapter that can be reused in search
private final LibraryFragment mParentActivity;
private List<Monster> mValues;
private final boolean mTwoPane;
private final Context mContext;
private final ItemCallback mOnDelete;
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
Monster monster = (Monster) view.getTag();
// TODO: I would like to call navigateToMonsterDetail(item.id) here
if (mTwoPane) {
// TODO: Figure out how to navigate to a MonsterDetailFragment when in two pane view.
// Bundle arguments = new Bundle();
// arguments.putString(ItemDetailFragment.ARG_ITEM_ID, monster.id.toString());
// ItemDetailFragment fragment = new ItemDetailFragment();
// fragment.setArguments(arguments);
// mParentActivity.getSupportFragmentManager().beginTransaction()
// .replace(R.id.item_detail_container, fragment)
// .commit();
} else {
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monster.id.toString());
Navigation.findNavController(view).navigate(action);
}
}
};
public MonsterListRecyclerViewAdapter(LibraryFragment parent,
Flowable<List<Monster>> itemsObservable,
ItemCallback onDelete,
boolean twoPane) {
mValues = new ArrayList<>();
mParentActivity = parent;
mTwoPane = twoPane;
mContext = parent.getContext();
mOnDelete = onDelete;
itemsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> {
mValues = monsters;
notifyDataSetChanged();
});
}
@Override
public ViewHolder onCreateViewHolder(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 ViewHolder holder, int position) {
holder.mIdView.setText(mValues.get(position).id.toString().substring(0, 6));
holder.mContentView.setText(mValues.get(position).name);
holder.itemView.setTag(mValues.get(position));
holder.itemView.setOnClickListener(mOnClickListener);
}
@Override
public int getItemCount() {
return mValues.size();
}
public Context getContext() {
return mContext;
}
class ViewHolder extends RecyclerView.ViewHolder {
final TextView mIdView;
final TextView mContentView;
ViewHolder(View view) {
super(view);
mIdView = view.findViewById(R.id.id_text);
mContentView = view.findViewById(R.id.content);
}
}
public void deleteItem(int position) {
if (mOnDelete != null) {
Monster monster = mValues.get(position);
mOnDelete.onItem(monster);
}
}
}

View File

@@ -7,6 +7,8 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.R;
@@ -21,7 +23,12 @@ public class CollectionsFragment extends MCFragment {
collectionsViewModel = new ViewModelProvider(this).get(CollectionsViewModel.class); collectionsViewModel = new ViewModelProvider(this).get(CollectionsViewModel.class);
View root = inflater.inflate(R.layout.fragment_collections, container, false); View root = inflater.inflate(R.layout.fragment_collections, container, false);
final TextView textView = root.findViewById(R.id.text_collections); final TextView textView = root.findViewById(R.id.text_collections);
collectionsViewModel.getText().observe(getViewLifecycleOwner(), textView::setText); collectionsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
textView.setText(s);
}
});
return root; return root;
} }
} }

View File

@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel;
public class CollectionsViewModel extends ViewModel { public class CollectionsViewModel extends ViewModel {
private final MutableLiveData<String> mText; private MutableLiveData<String> mText;
public CollectionsViewModel() { public CollectionsViewModel() {
mText = new MutableLiveData<>(); mText = new MutableLiveData<>();

View File

@@ -35,12 +35,11 @@ public class AbilityScorePicker extends LinearLayout {
// TODO: use this as default but allow setting via attribute // TODO: use this as default but allow setting via attribute
mLabel = "Ability Score"; mLabel = "Ability Score";
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AbilityScorePicker, 0, 0); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Stepper, 0, 0);
String label = a.getString(R.styleable.AbilityScorePicker_label); String label = a.getString(R.styleable.Stepper_label);
if (label != null) { if (label != null) {
mLabel = label; mLabel = label;
} }
a.recycle();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View root = inflater.inflate(R.layout.component_ability_score_picker, this, true); View root = inflater.inflate(R.layout.component_ability_score_picker, this, true);
@@ -70,17 +69,18 @@ public class AbilityScorePicker extends LinearLayout {
mHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { mHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
setValue((AbilityScore) parent.getItemAtPosition(position)); mSelectedValue = (AbilityScore) parent.getItemAtPosition(position);
} }
@Override @Override
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {
setValue(mSelectedValue = AbilityScore.STRENGTH); mSelectedValue = AbilityScore.STRENGTH;
} }
}); });
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue)); mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
setValue(AbilityScore.STRENGTH); setValue(AbilityScore.STRENGTH);
// TODO: listen for changes on the component to update mSelectedValue;
} }
public AbilityScorePicker(@NonNull Context context) { public AbilityScorePicker(@NonNull Context context) {
@@ -93,8 +93,7 @@ public class AbilityScorePicker extends LinearLayout {
public void setValue(AbilityScore value) { public void setValue(AbilityScore value) {
if (value != mSelectedValue) { if (value != mSelectedValue) {
mSelectedValue = value; mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), value));
if (mOnValueChangedListener != null) { if (mOnValueChangedListener != null) {
mOnValueChangedListener.onValueChanged(value); mOnValueChangedListener.onValueChanged(value);
} }
@@ -125,7 +124,7 @@ public class AbilityScorePicker extends LinearLayout {
private final Spinner spinner; private final Spinner spinner;
private final TextView label; private final TextView label;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
spinner = root.findViewById(R.id.spinner); spinner = root.findViewById(R.id.spinner);
label = root.findViewById(R.id.label); label = root.findViewById(R.id.label);
} }

View File

@@ -33,9 +33,9 @@ public class AdvantagePicker extends ConstraintLayout {
setValue(AdvantageType.NONE); setValue(AdvantageType.NONE);
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> { mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
if (R.id.hasAdvantage == checkedId) { if (R.id.advantage == checkedId) {
setValue(AdvantageType.ADVANTAGE); setValue(AdvantageType.ADVANTAGE);
} else if (R.id.hasDisadvantage == checkedId) { } else if (R.id.disadvantage == checkedId) {
setValue(AdvantageType.DISADVANTAGE); setValue(AdvantageType.DISADVANTAGE);
} else { } else {
setValue(AdvantageType.NONE); setValue(AdvantageType.NONE);
@@ -60,11 +60,11 @@ public class AdvantagePicker extends ConstraintLayout {
} }
final int checkedId = mHolder.group.getCheckedRadioButtonId(); final int checkedId = mHolder.group.getCheckedRadioButtonId();
if (mSelectedValue == AdvantageType.ADVANTAGE) { if (mSelectedValue == AdvantageType.ADVANTAGE) {
if (checkedId != R.id.hasAdvantage) { if (checkedId != R.id.advantage) {
mHolder.advantage.setChecked(true); mHolder.advantage.setChecked(true);
} }
} else if (mSelectedValue == AdvantageType.DISADVANTAGE) { } else if (mSelectedValue == AdvantageType.DISADVANTAGE) {
if (checkedId != R.id.hasDisadvantage) { if (checkedId != R.id.disadvantage) {
mHolder.disadvantage.setChecked(true); mHolder.disadvantage.setChecked(true);
} }
} else { } else {
@@ -88,11 +88,11 @@ public class AdvantagePicker extends ConstraintLayout {
final MaterialRadioButton advantage; final MaterialRadioButton advantage;
final MaterialRadioButton disadvantage; final MaterialRadioButton disadvantage;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
group = root.findViewById(R.id.group); group = root.findViewById(R.id.group);
none = root.findViewById(R.id.hasNoAdvantage); none = root.findViewById(R.id.none);
advantage = root.findViewById(R.id.hasAdvantage); advantage = root.findViewById(R.id.advantage);
disadvantage = root.findViewById(R.id.hasDisadvantage); disadvantage = root.findViewById(R.id.disadvantage);
} }
} }
} }

View File

@@ -88,7 +88,7 @@ public class ProficiencyPicker extends ConstraintLayout {
final MaterialRadioButton proficient; final MaterialRadioButton proficient;
final MaterialRadioButton expertise; final MaterialRadioButton expertise;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
group = root.findViewById(R.id.group); group = root.findViewById(R.id.group);
none = root.findViewById(R.id.none); none = root.findViewById(R.id.none);
proficient = root.findViewById(R.id.proficient); proficient = root.findViewById(R.id.proficient);

View File

@@ -13,11 +13,11 @@ import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.utils.Logger;
import java.util.Objects; import java.util.Objects;
@SuppressWarnings("unused")
public class Stepper extends ConstraintLayout { public class Stepper extends ConstraintLayout {
private final ViewHolder mHolder; private final ViewHolder mHolder;
private int mCurrentValue; private int mCurrentValue;
@@ -46,7 +46,6 @@ public class Stepper extends ConstraintLayout {
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setValue(mCurrentValue); setValue(mCurrentValue);
updateDisplayedValue();
mHolder.increment.setOnClickListener(v -> setValue(mCurrentValue + mStep)); mHolder.increment.setOnClickListener(v -> setValue(mCurrentValue + mStep));
mHolder.decrement.setOnClickListener(v -> setValue(mCurrentValue - mStep)); mHolder.decrement.setOnClickListener(v -> setValue(mCurrentValue - mStep));
@@ -75,22 +74,19 @@ public class Stepper extends ConstraintLayout {
public void setValue(int value) { public void setValue(int value) {
int oldValue = this.mCurrentValue; int oldValue = this.mCurrentValue;
int newValue = Math.min(mMaxValue, Math.max(mMinValue, value)); int newValue = Math.min(mMaxValue, Math.max(mMinValue, value));
Logger.logDebug(String.format("Setting stepper value value: %d, oldValue: %d, newValue: %d", value, oldValue, newValue));
if (newValue != oldValue) { if (newValue != oldValue) {
this.mCurrentValue = newValue; this.mCurrentValue = newValue;
if (mOnValueChangeListener != null) { if (mOnValueChangeListener != null) {
mOnValueChangeListener.onChange(newValue, oldValue); mOnValueChangeListener.onChange(newValue, oldValue);
} }
updateDisplayedValue();
}
}
private void updateDisplayedValue() {
if (mOnFormatValueCallback != null) { if (mOnFormatValueCallback != null) {
mHolder.text.setText(mOnFormatValueCallback.onFormatValue(this.mCurrentValue)); mHolder.text.setText(mOnFormatValueCallback.onFormatValue(this.mCurrentValue));
} else { } else {
mHolder.text.setText(String.valueOf(this.mCurrentValue)); mHolder.text.setText(String.valueOf(this.mCurrentValue));
} }
} }
}
public void setOnValueChangeListener(OnValueChangeListener listener) { public void setOnValueChangeListener(OnValueChangeListener listener) {
mOnValueChangeListener = listener; mOnValueChangeListener = listener;
@@ -98,7 +94,6 @@ public class Stepper extends ConstraintLayout {
public void setOnFormatValueCallback(OnFormatValueCallback callback) { public void setOnFormatValueCallback(OnFormatValueCallback callback) {
mOnFormatValueCallback = callback; mOnFormatValueCallback = callback;
updateDisplayedValue();
} }
public int getStep() { public int getStep() {
@@ -139,7 +134,7 @@ public class Stepper extends ConstraintLayout {
final Button increment; final Button increment;
final Button decrement; final Button decrement;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
text = root.findViewById(R.id.text); text = root.findViewById(R.id.text);
label = root.findViewById(R.id.label); label = root.findViewById(R.id.label);
increment = root.findViewById(R.id.increment); increment = root.findViewById(R.id.increment);

View File

@@ -20,16 +20,13 @@ import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.Logger;
import java.util.List; 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 { public class DashboardFragment extends MCFragment {
private DashboardViewModel mViewModel; private DashboardViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
private DashboardRecyclerViewAdapter mAdapter; private DashboardRecyclerViewAdapter mAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { ViewGroup container, Bundle savedInstanceState) {
mViewModel = new ViewModelProvider(this).get(DashboardViewModel.class); mViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
@@ -38,18 +35,11 @@ public class DashboardFragment extends MCFragment {
setupRecyclerView(mHolder.list); setupRecyclerView(mHolder.list);
getMonsterRepository()
.getMonsters()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(monsters -> mViewModel.setMonsters(monsters));
return root; return root;
} }
private void setupRecyclerView(@NonNull RecyclerView recyclerView) { private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396); int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396);
Logger.logWTF(String.format(Locale.US, "Setting column count to %d", columnCount));
Context context = requireContext(); Context context = requireContext();
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount); GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);

View File

@@ -20,6 +20,7 @@ import com.majinnaibu.monstercards.databinding.CardMonsterBinding;
import com.majinnaibu.monstercards.helpers.CommonMarkHelper; import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.models.Trait; import com.majinnaibu.monstercards.models.Trait;
import com.majinnaibu.monstercards.utils.ItemCallback;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.Logger;
import java.util.Locale; import java.util.Locale;
@@ -28,17 +29,17 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() { private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
@Override @Override
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) { public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return oldItem.id.equals(newItem.id); return Monster.areItemsTheSame(oldItem, newItem);
} }
@Override @Override
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) { public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return oldItem.equals(newItem); return Monster.areContentsTheSame(oldItem, newItem);
} }
}; };
private final ItemCallback mOnClick; private final ItemCallback<Monster> mOnClick;
protected DashboardRecyclerViewAdapter(ItemCallback onClick) { protected DashboardRecyclerViewAdapter(ItemCallback<Monster> onClick) {
super(DIFF_CALLBACK); super(DIFF_CALLBACK);
mOnClick = onClick; mOnClick = onClick;
} }
@@ -51,7 +52,6 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Logger.logUnimplementedMethod();
Monster monster = getItem(position); Monster monster = getItem(position);
holder.monster = monster; holder.monster = monster;
holder.name.setText(monster.name); holder.name.setText(monster.name);
@@ -120,15 +120,11 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) { if (mOnClick != null) {
mOnClick.onItemCallback(holder.monster); mOnClick.onItem(holder.monster);
} }
}); });
} }
public interface ItemCallback {
void onItemCallback(Monster monster);
}
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
public final TextView name; public final TextView name;
public final TextView meta; public final TextView meta;
@@ -242,7 +238,6 @@ public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, Dashboard
@NonNull @NonNull
public static String getChallengeRatingAbbreviation(@NonNull ChallengeRating challengeRating) { public static String getChallengeRatingAbbreviation(@NonNull ChallengeRating challengeRating) {
Logger.logUnimplementedMethod();
switch (challengeRating) { switch (challengeRating) {
case CUSTOM: case CUSTOM:
return "*"; return "*";

View File

@@ -1,26 +1,50 @@
package com.majinnaibu.monstercards.ui.dashboard; package com.majinnaibu.monstercards.ui.dashboard;
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.majinnaibu.monstercards.AppDatabase;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class DashboardViewModel extends ViewModel { import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subscribers.DisposableSubscriber;
public class DashboardViewModel extends AndroidViewModel {
private final AppDatabase mDB;
private final MutableLiveData<List<Monster>> mMonsters; private final MutableLiveData<List<Monster>> mMonsters;
public DashboardViewModel() { public DashboardViewModel(Application application) {
super(application);
mDB = AppDatabase.getInstance(application);
mMonsters = new MutableLiveData<>(new ArrayList<>()); mMonsters = new MutableLiveData<>(new ArrayList<>());
mDB.monsterDAO()
.getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSubscriber<List<Monster>>() {
@Override
public void onNext(List<Monster> monsters) {
mMonsters.setValue(monsters);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
} }
public LiveData<List<Monster>> getMonsters() { public LiveData<List<Monster>> getMonsters() {
return mMonsters; return mMonsters;
} }
public void setMonsters(List<Monster> monsters) {
mMonsters.setValue(monsters);
}
} }

View File

@@ -5,7 +5,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
@@ -15,9 +15,7 @@ import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.ui.components.Stepper; import com.majinnaibu.monstercards.ui.components.Stepper;
import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.MCFragment;
import java.util.Locale; public class EditAbilityScoresFragment extends Fragment {
public class EditAbilityScoresFragment extends MCFragment {
private final String ABILITY_SCORE_FORMAT = "%d (%+d)"; private final String ABILITY_SCORE_FORMAT = "%d (%+d)";
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
@@ -27,37 +25,38 @@ public class EditAbilityScoresFragment extends MCFragment {
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_ability_scores, container, false); View root = inflater.inflate(R.layout.fragment_edit_ability_scores, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_ability_scores));
mViewModel.getStrength().observe(getViewLifecycleOwner(), value -> mHolder.strength.setValue(value)); mViewModel.getStrength().observe(getViewLifecycleOwner(), value -> mHolder.strength.setValue(value));
mHolder.strength.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setStrength(newValue)); mHolder.strength.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setStrength(newValue));
mHolder.strength.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value))); mHolder.strength.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
mViewModel.getDexterity().observe(getViewLifecycleOwner(), value -> mHolder.dexterity.setValue(value)); mViewModel.getDexterity().observe(getViewLifecycleOwner(), value -> mHolder.dexterity.setValue(value));
mHolder.dexterity.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setDexterity(newValue)); mHolder.dexterity.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setDexterity(newValue));
mHolder.dexterity.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value))); mHolder.dexterity.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
mViewModel.getConstitution().observe(getViewLifecycleOwner(), value -> mHolder.constitution.setValue(value)); mViewModel.getConstitution().observe(getViewLifecycleOwner(), value -> mHolder.constitution.setValue(value));
mHolder.constitution.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setConstitution(newValue)); mHolder.constitution.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setConstitution(newValue));
mHolder.constitution.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value))); mHolder.constitution.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), value -> mHolder.intelligence.setValue(value)); mViewModel.getIntelligence().observe(getViewLifecycleOwner(), value -> mHolder.intelligence.setValue(value));
mHolder.intelligence.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setIntelligence(newValue)); mHolder.intelligence.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setIntelligence(newValue));
mHolder.intelligence.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value))); mHolder.intelligence.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
mViewModel.getWisdom().observe(getViewLifecycleOwner(), value -> mHolder.wisdom.setValue(value)); mViewModel.getWisdom().observe(getViewLifecycleOwner(), value -> mHolder.wisdom.setValue(value));
mHolder.wisdom.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWisdom(newValue)); mHolder.wisdom.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWisdom(newValue));
mHolder.wisdom.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value))); mHolder.wisdom.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
mViewModel.getCharisma().observe(getViewLifecycleOwner(), value -> mHolder.charisma.setValue(value)); mViewModel.getCharisma().observe(getViewLifecycleOwner(), value -> mHolder.charisma.setValue(value));
mHolder.charisma.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setCharisma(newValue)); mHolder.charisma.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setCharisma(newValue));
mHolder.charisma.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value))); mHolder.charisma.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
return root; return root;
} }
@@ -70,7 +69,7 @@ public class EditAbilityScoresFragment extends MCFragment {
final Stepper wisdom; final Stepper wisdom;
final Stepper charisma; final Stepper charisma;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
strength = root.findViewById(R.id.strength); strength = root.findViewById(R.id.strength);
dexterity = root.findViewById(R.id.dexterity); dexterity = root.findViewById(R.id.dexterity);
constitution = root.findViewById(R.id.constitution); constitution = root.findViewById(R.id.constitution);

View File

@@ -30,13 +30,14 @@ public class EditArmorFragment extends MCFragment {
private ViewHolder mHolder; private ViewHolder mHolder;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_armor, container, false); View root = inflater.inflate(R.layout.fragment_edit_armor, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_armor));
mHolder.armorType.setAdapter(new ArrayAdapter<ArmorType>(requireContext(), R.layout.dropdown_list_item, ArmorType.values()) { mHolder.armorType.setAdapter(new ArrayAdapter<ArmorType>(requireContext(), R.layout.dropdown_list_item, ArmorType.values()) {
@NonNull @NonNull
@@ -93,7 +94,7 @@ public class EditArmorFragment extends MCFragment {
private final Stepper shieldBonus; private final Stepper shieldBonus;
private final EditText customArmor; private final EditText customArmor;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
armorType = root.findViewById(R.id.armorType); armorType = root.findViewById(R.id.armorType);
naturalArmorBonus = root.findViewById(R.id.naturalArmorBonus); naturalArmorBonus = root.findViewById(R.id.naturalArmorBonus);
hasShield = root.findViewById(R.id.hasShield); hasShield = root.findViewById(R.id.hasShield);

View File

@@ -6,7 +6,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
@@ -29,12 +28,13 @@ public class EditBasicInfoFragment extends MCFragment {
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_basic_info, container, false); View root = inflater.inflate(R.layout.fragment_edit_basic_info, container, false);
setTitle(getString(R.string.title_edit_basic_info));
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
mHolder.name.setText(mViewModel.getName().getValue()); mHolder.name.setText(mViewModel.getName().getValue());
@@ -74,7 +74,7 @@ public class EditBasicInfoFragment extends MCFragment {
private final Stepper hitDice; private final Stepper hitDice;
private final SwitchMaterial hasCustomHitPoints; private final SwitchMaterial hasCustomHitPoints;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
name = root.findViewById(R.id.name); name = root.findViewById(R.id.name);
size = root.findViewById(R.id.size); size = root.findViewById(R.id.size);
type = root.findViewById(R.id.type); type = root.findViewById(R.id.type);

View File

@@ -28,13 +28,14 @@ public class EditChallengeRatingFragment extends MCFragment {
private ViewHolder mHolder; private ViewHolder mHolder;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_challenge_rating, container, false); View root = inflater.inflate(R.layout.fragment_edit_challenge_rating, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_challenge_rating));
mHolder.challengeRating.setAdapter(new ArrayAdapter<ChallengeRating>(requireContext(), R.layout.dropdown_list_item, ChallengeRating.values()) { mHolder.challengeRating.setAdapter(new ArrayAdapter<ChallengeRating>(requireContext(), R.layout.dropdown_list_item, ChallengeRating.values()) {
@NonNull @NonNull
@@ -82,7 +83,7 @@ public class EditChallengeRatingFragment extends MCFragment {
final EditText customChallengeRatingDescription; final EditText customChallengeRatingDescription;
final EditText customProficiencyBonus; final EditText customProficiencyBonus;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
challengeRating = root.findViewById(R.id.challengeRating); challengeRating = root.findViewById(R.id.challengeRating);
customChallengeRatingDescription = root.findViewById(R.id.customChallengeRatingDescription); customChallengeRatingDescription = root.findViewById(R.id.customChallengeRatingDescription);
customProficiencyBonus = root.findViewById(R.id.customProficiencyBonus); customProficiencyBonus = root.findViewById(R.id.customProficiencyBonus);

View File

@@ -44,12 +44,13 @@ public class EditLanguageFragment extends MCFragment {
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_language, container, false); View root = inflater.inflate(R.layout.fragment_edit_language, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_language));
mHolder.name.setText(mViewModel.getName().getValue()); mHolder.name.setText(mViewModel.getName().getValue());
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString()))); mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
@@ -80,7 +81,7 @@ public class EditLanguageFragment extends MCFragment {
EditText name; EditText name;
SwitchCompat canSpeak; SwitchCompat canSpeak;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
name = root.findViewById(R.id.name); name = root.findViewById(R.id.name);
canSpeak = root.findViewById(R.id.canSpeak); canSpeak = root.findViewById(R.id.canSpeak);
} }

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.ui.editmonster; package com.majinnaibu.monstercards.ui.editmonster;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import com.majinnaibu.monstercards.models.Language; import com.majinnaibu.monstercards.models.Language;
@@ -19,7 +18,7 @@ public class EditLanguageViewModel extends ChangeTrackedViewModel {
mLanguage = new ChangeTrackedLiveData<>(makeLanguage(), this::makeDirty); mLanguage = new ChangeTrackedLiveData<>(makeLanguage(), this::makeDirty);
} }
public void copyFromLanguage(@NonNull Language language) { public void copyFromLanguage(Language language) {
mName.resetValue(language.getName()); mName.resetValue(language.getName());
mCanSpeak.resetValue(language.getSpeaks()); mCanSpeak.resetValue(language.getSpeaks());
makeClean(); makeClean();
@@ -59,7 +58,6 @@ public class EditLanguageViewModel extends ChangeTrackedViewModel {
return getCanSpeakValue(false); return getCanSpeakValue(false);
} }
@NonNull
private Language makeLanguage() { private Language makeLanguage() {
Boolean boxedValue = mCanSpeak.getValue(); Boolean boxedValue = mCanSpeak.getValue();
boolean canSpeak = boxedValue != null && boxedValue; boolean canSpeak = boxedValue != null && boxedValue;

View File

@@ -30,19 +30,20 @@ public class EditLanguagesFragment extends MCFragment {
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
private void navigateToEditLanguage(@NonNull Language language) { private void navigateToEditLanguage(Language language) {
NavDirections action = EditLanguagesFragmentDirections.actionEditLanguagesFragmentToEditLanguageFragment(language.getName(), language.getSpeaks()); NavDirections action = EditLanguagesFragmentDirections.actionEditLanguagesFragmentToEditLanguageFragment(language.getName(), language.getSpeaks());
Navigation.findNavController(requireView()).navigate(action); Navigation.findNavController(requireView()).navigate(action);
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_languages_list, container, false); View root = inflater.inflate(R.layout.fragment_edit_languages_list, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_languages));
setupRecyclerView(mHolder.list); setupRecyclerView(mHolder.list);
setupAddLanguageButton(mHolder.addLanguage); setupAddLanguageButton(mHolder.addLanguage);
@@ -93,7 +94,7 @@ public class EditLanguagesFragment extends MCFragment {
RecyclerView list; RecyclerView list;
FloatingActionButton addLanguage; FloatingActionButton addLanguage;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
this.list = root.findViewById(R.id.list); this.list = root.findViewById(R.id.list);
this.addLanguage = root.findViewById(R.id.add_language); this.addLanguage = root.findViewById(R.id.add_language);
} }

View File

@@ -10,16 +10,16 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListHeaderBinding; import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListHeaderBinding;
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListItemBinding;
import com.majinnaibu.monstercards.models.Language; import com.majinnaibu.monstercards.models.Language;
import com.majinnaibu.monstercards.ui.components.Stepper; import com.majinnaibu.monstercards.ui.components.Stepper;
import com.majinnaibu.monstercards.utils.ItemCallback;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final List<Language> mValues; private final List<Language> mValues;
private final ItemCallback mOnClick; private final ItemCallback<Language> mOnClick;
private final int mTelepathyRange; private final int mTelepathyRange;
private final String mUnderstandsBut; private final String mUnderstandsBut;
private final Stepper.OnValueChangeListener mOnTelepathyRangeChanged; private final Stepper.OnValueChangeListener mOnTelepathyRangeChanged;
@@ -29,7 +29,7 @@ public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<Recyc
private final int ITEM_VIEW_TYPE = 2; private final int ITEM_VIEW_TYPE = 2;
private final String DISTANCE_IN_FEET_FORMAT = "%d ft."; 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) { public EditLanguagesRecyclerViewAdapter(List<Language> items, ItemCallback<Language> onClick, int telepathyRange, Stepper.OnValueChangeListener telepathyRangeChangedListener, String understandsBut, TextWatcher understandsButChangedListener) {
mValues = items; mValues = items;
mOnClick = onClick; mOnClick = onClick;
mTelepathyRange = telepathyRange; mTelepathyRange = telepathyRange;
@@ -44,7 +44,7 @@ public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<Recyc
if (viewType == HEADER_VIEW_TYPE) { if (viewType == HEADER_VIEW_TYPE) {
return new HeaderViewHolder(FragmentEditLanguagesListHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); return new HeaderViewHolder(FragmentEditLanguagesListHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
} }
return new ItemViewHolder(FragmentEditLanguagesListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); return new ItemViewHolder(com.majinnaibu.monstercards.databinding.SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
} }
@Override @Override
@@ -62,7 +62,7 @@ public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<Recyc
itemViewHolder.mContentView.setText(itemViewHolder.mItem.getName()); itemViewHolder.mContentView.setText(itemViewHolder.mItem.getName());
itemViewHolder.itemView.setOnClickListener(view -> { itemViewHolder.itemView.setOnClickListener(view -> {
if (mOnClick != null) { if (mOnClick != null) {
mOnClick.onItemCallback(itemViewHolder.mItem); mOnClick.onItem(itemViewHolder.mItem);
} }
}); });
} }
@@ -81,10 +81,6 @@ public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<Recyc
return ITEM_VIEW_TYPE; return ITEM_VIEW_TYPE;
} }
public interface ItemCallback {
void onItemCallback(Language language);
}
public static class HeaderViewHolder extends RecyclerView.ViewHolder { public static class HeaderViewHolder extends RecyclerView.ViewHolder {
public final Stepper telepathy; public final Stepper telepathy;
public final EditText understandsBut; public final EditText understandsBut;
@@ -100,7 +96,7 @@ public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<Recyc
public final TextView mContentView; public final TextView mContentView;
public Language mItem; public Language mItem;
public ItemViewHolder(@NonNull FragmentEditLanguagesListItemBinding binding) { public ItemViewHolder(@NonNull com.majinnaibu.monstercards.databinding.SimpleListItemBinding binding) {
super(binding.getRoot()); super(binding.getRoot());
mContentView = binding.content; mContentView = binding.content;
} }

View File

@@ -55,7 +55,7 @@ public class EditMonsterFragment extends MCFragment {
View root = inflater.inflate(R.layout.fragment_edit_monster, container, false); View root = inflater.inflate(R.layout.fragment_edit_monster, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_editMonster_fmt, getString(R.string.default_monster_name))); setTitle(getString(R.string.title_edit_monster, getString(R.string.default_monster_name)));
// TODO: Show a loading spinner until we have the monster loaded. // TODO: Show a loading spinner until we have the monster loaded.
if (mViewModel.hasError() || !mViewModel.hasLoaded() || !Objects.equals(mViewModel.getMonsterId().getValue(), monsterId)) { if (mViewModel.hasError() || !mViewModel.hasLoaded() || !Objects.equals(mViewModel.getMonsterId().getValue(), monsterId)) {
@@ -67,7 +67,7 @@ public class EditMonsterFragment extends MCFragment {
mViewModel.setHasLoaded(true); mViewModel.setHasLoaded(true);
mViewModel.setHasError(false); mViewModel.setHasError(false);
mViewModel.copyFromMonster(monster); mViewModel.copyFromMonster(monster);
setTitle(getString(R.string.title_editMonster_fmt, monster.name)); setTitle(getString(R.string.title_edit_monster, monster.name));
dispose(); dispose();
} }
@@ -247,7 +247,7 @@ public class EditMonsterFragment extends MCFragment {
TextView lairActions; TextView lairActions;
TextView regionalActions; TextView regionalActions;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
basicInfoButton = root.findViewById(R.id.basicInfo); basicInfoButton = root.findViewById(R.id.basicInfo);
armorButton = root.findViewById(R.id.armor); armorButton = root.findViewById(R.id.armor);
speedButton = root.findViewById(R.id.speed); speedButton = root.findViewById(R.id.speed);

View File

@@ -28,7 +28,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@SuppressWarnings({"ConstantConditions"}) @SuppressWarnings({"ConstantConditions", "unused"})
public class EditMonsterViewModel extends ChangeTrackedViewModel { public class EditMonsterViewModel extends ChangeTrackedViewModel {
private final ChangeTrackedLiveData<UUID> mMonsterId; private final ChangeTrackedLiveData<UUID> mMonsterId;
private final MutableLiveData<Boolean> mHasError; private final MutableLiveData<Boolean> mHasError;
@@ -158,7 +158,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mRegionalActions = new ChangeTrackedLiveData<>(new ArrayList<>(), this::makeDirty); mRegionalActions = new ChangeTrackedLiveData<>(new ArrayList<>(), this::makeDirty);
} }
public void copyFromMonster(@NonNull Monster monster) { public void copyFromMonster(Monster monster) {
mMonsterId.resetValue(monster.id); mMonsterId.resetValue(monster.id);
mName.resetValue(monster.name); mName.resetValue(monster.name);
mSize.resetValue(monster.size); mSize.resetValue(monster.size);
@@ -206,7 +206,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mUnderstandsButDescription.resetValue(monster.understandsButDescription); mUnderstandsButDescription.resetValue(monster.understandsButDescription);
ArrayList<Skill> skills = new ArrayList<>(monster.skills); ArrayList<Skill> skills = new ArrayList<>(monster.skills);
Collections.sort(skills, Skill::compareTo); Collections.sort(skills, (skill1, skill2) -> skill1.name.compareToIgnoreCase(skill2.name));
mSkills.resetValue(skills); mSkills.resetValue(skills);
ArrayList<String> senses = new ArrayList<>(monster.senses); ArrayList<String> senses = new ArrayList<>(monster.senses);
Collections.sort(senses, String::compareToIgnoreCase); Collections.sort(senses, String::compareToIgnoreCase);
@@ -224,7 +224,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
Collections.sort(conditionImmunities, String::compareToIgnoreCase); Collections.sort(conditionImmunities, String::compareToIgnoreCase);
mConditionImmunities.resetValue(conditionImmunities); mConditionImmunities.resetValue(conditionImmunities);
ArrayList<Language> languages = new ArrayList<>(monster.languages); ArrayList<Language> languages = new ArrayList<>(monster.languages);
Collections.sort(languages, Language::compareTo); Collections.sort(languages, (lang1, lang2) -> lang1.getName().compareToIgnoreCase(lang2.getName()));
mLanguages.resetValue(languages); mLanguages.resetValue(languages);
mAbilities.resetValue(new ArrayList<>(monster.abilities)); mAbilities.resetValue(new ArrayList<>(monster.abilities));
mActions.resetValue(new ArrayList<>(monster.actions)); mActions.resetValue(new ArrayList<>(monster.actions));
@@ -247,6 +247,10 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return mMonsterId; return mMonsterId;
} }
public LiveData<String> getErrorMessage() {
return mErrorMessage;
}
public void setErrorMessage(@NonNull String errorMessage) { public void setErrorMessage(@NonNull String errorMessage) {
mErrorMessage.setValue(errorMessage); mErrorMessage.setValue(errorMessage);
} }
@@ -315,22 +319,48 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mCustomHitPoints.setValue(customHitPoints); mCustomHitPoints.setValue(customHitPoints);
} }
public LiveData<Integer> getHitDice() {
return mHitDice;
}
public void setHitDice(int hitDice) { public void setHitDice(int hitDice) {
mHitDice.setValue(hitDice); mHitDice.setValue(hitDice);
} }
public void setHitDice(String hitDice) {
Integer parsedHitDice = StringHelper.parseInt(hitDice);
this.setHitDice(parsedHitDice != null ? parsedHitDice : 0);
}
public int getHitDiceUnboxed() { public int getHitDiceUnboxed() {
return Helpers.unboxInteger(mHitDice.getValue(), 1); return Helpers.unboxInteger(mHitDice.getValue(), 1);
} }
public String getHitDiceValueAsString() {
return mHitDice.getValue().toString();
}
public LiveData<Integer> getNaturalArmorBonus() {
return mNaturalArmorBonus;
}
public void setNaturalArmorBonus(int naturalArmorBonus) { public void setNaturalArmorBonus(int naturalArmorBonus) {
mNaturalArmorBonus.setValue(naturalArmorBonus); mNaturalArmorBonus.setValue(naturalArmorBonus);
} }
public void setNaturalArmorBonus(String naturalArmorBonus) {
Integer parsedValue = StringHelper.parseInt(naturalArmorBonus);
this.setNaturalArmorBonus(parsedValue != null ? parsedValue : 0);
}
public int getNaturalArmorBonusUnboxed() { public int getNaturalArmorBonusUnboxed() {
return Helpers.unboxInteger(mNaturalArmorBonus.getValue(), 0); return Helpers.unboxInteger(mNaturalArmorBonus.getValue(), 0);
} }
public String getNaturalArmorBonusValueAsString() {
return mNaturalArmorBonus.getValue().toString();
}
public LiveData<Boolean> getHasCustomHitPoints() { public LiveData<Boolean> getHasCustomHitPoints() {
return mHasCustomHitPoints; return mHasCustomHitPoints;
} }
@@ -351,6 +381,10 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mArmorType.setValue(armorType); mArmorType.setValue(armorType);
} }
public LiveData<Boolean> getHasShield() {
return mHasShield;
}
public void setHasShield(boolean hasShield) { public void setHasShield(boolean hasShield) {
mHasShield.setValue(hasShield); mHasShield.setValue(hasShield);
} }
@@ -359,10 +393,19 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return mHasShield.getValue(); return mHasShield.getValue();
} }
public LiveData<Integer> getShieldBonus() {
return mShieldBonus;
}
public void setShieldBonus(int shieldBonus) { public void setShieldBonus(int shieldBonus) {
mShieldBonus.setValue(shieldBonus); mShieldBonus.setValue(shieldBonus);
} }
public void setShieldBonus(String shieldBonus) {
Integer parsedValue = StringHelper.parseInt(shieldBonus);
this.setShieldBonus(parsedValue != null ? parsedValue : 0);
}
public int getShieldBonusUnboxed() { public int getShieldBonusUnboxed() {
return Helpers.unboxInteger(mShieldBonus.getValue(), 0); return Helpers.unboxInteger(mShieldBonus.getValue(), 0);
} }
@@ -387,6 +430,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mWalkSpeed.setValue(walkSpeed); mWalkSpeed.setValue(walkSpeed);
} }
public void incrementWalkSpeed() {
setWalkSpeed(mWalkSpeed.getValue() + 5);
}
public void decrementWalkSpeed() {
setWalkSpeed(mWalkSpeed.getValue() - 5);
}
public LiveData<Integer> getBurrowSpeed() { public LiveData<Integer> getBurrowSpeed() {
return mBurrowSpeed; return mBurrowSpeed;
} }
@@ -443,6 +494,38 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mCustomSpeed.setValue(customSpeed); mCustomSpeed.setValue(customSpeed);
} }
public void incrementBurrowSpeed() {
setBurrowSpeed(mBurrowSpeed.getValue() + 5);
}
public void decrementBurrowSpeed() {
setBurrowSpeed(mBurrowSpeed.getValue() - 5);
}
public void incrementClimbSpeed() {
setClimbSpeed(mClimbSpeed.getValue() + 5);
}
public void decrementClimbSpeed() {
setClimbSpeed(mClimbSpeed.getValue() - 5);
}
public void incrementFlySpeed() {
setFlySpeed(mFlySpeed.getValue() + 5);
}
public void decrementFlySpeed() {
setFlySpeed(mFlySpeed.getValue() - 5);
}
public void incrementSwimSpeed() {
setSwimSpeed(mSwimSpeed.getValue() + 5);
}
public void decrementSwimSpeed() {
setSwimSpeed(mSwimSpeed.getValue() - 5);
}
public LiveData<Integer> getStrength() { public LiveData<Integer> getStrength() {
return mStrength; return mStrength;
} }
@@ -451,6 +534,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mStrength.setValue(strength); mStrength.setValue(strength);
} }
public void incrementStrength() {
setStrength(mStrength.getValue() + 1);
}
public void decrementStrength() {
setStrength(mStrength.getValue() - 1);
}
public LiveData<Integer> getDexterity() { public LiveData<Integer> getDexterity() {
return mDexterity; return mDexterity;
} }
@@ -459,6 +550,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mDexterity.setValue(dexterity); mDexterity.setValue(dexterity);
} }
public void incrementDexterity() {
setDexterity(mDexterity.getValue() + 1);
}
public void decrementDexterity() {
setDexterity(mDexterity.getValue() - 1);
}
public LiveData<Integer> getConstitution() { public LiveData<Integer> getConstitution() {
return mConstitution; return mConstitution;
} }
@@ -467,6 +566,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mConstitution.setValue(constitution); mConstitution.setValue(constitution);
} }
public void incrementConstitution() {
setConstitution(mConstitution.getValue() + 1);
}
public void decrementConstitution() {
setConstitution(mConstitution.getValue() - 1);
}
public LiveData<Integer> getIntelligence() { public LiveData<Integer> getIntelligence() {
return mIntelligence; return mIntelligence;
} }
@@ -475,6 +582,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mIntelligence.setValue(intelligence); mIntelligence.setValue(intelligence);
} }
public void incrementIntelligence() {
setIntelligence(mIntelligence.getValue() + 1);
}
public void decrementIntelligence() {
setIntelligence(mIntelligence.getValue() - 1);
}
public LiveData<Integer> getWisdom() { public LiveData<Integer> getWisdom() {
return mWisdom; return mWisdom;
} }
@@ -483,6 +598,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mWisdom.setValue(wisdom); mWisdom.setValue(wisdom);
} }
public void incrementWisdom() {
setWisdom(mWisdom.getValue() + 1);
}
public void decrementWisdom() {
setWisdom(mWisdom.getValue() - 1);
}
public LiveData<Integer> getCharisma() { public LiveData<Integer> getCharisma() {
return mCharisma; return mCharisma;
} }
@@ -491,6 +614,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
mCharisma.setValue(charisma); mCharisma.setValue(charisma);
} }
public void incrementCharisma() {
setCharisma(mCharisma.getValue() + 1);
}
public void decrementCharisma() {
setCharisma(mCharisma.getValue() - 1);
}
public LiveData<ProficiencyType> getStrengthProficiency() { public LiveData<ProficiencyType> getStrengthProficiency() {
return mStrengthProficiency; return mStrengthProficiency;
} }
@@ -690,6 +821,22 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return mSenses; return mSenses;
} }
public List<String> getSensesArray() {
return mSenses.getValue();
}
public String addNewSense() {
return Helpers.addItemToList(mSenses, "", String::compareToIgnoreCase);
}
public void removeSense(int position) {
Helpers.removeFromList(mSenses, position);
}
public void replaceSense(String oldSense, String newSense) {
Helpers.replaceItemInList(mSenses, oldSense, newSense, String::compareToIgnoreCase);
}
public LiveData<List<String>> getDamageImmunities() { public LiveData<List<String>> getDamageImmunities() {
return mDamageImmunities; return mDamageImmunities;
} }
@@ -698,10 +845,78 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return mDamageImmunities.getValue(); return mDamageImmunities.getValue();
} }
public String addNewDamageImmunity() {
return Helpers.addStringToList("", mDamageImmunities);
}
public void removeDamageImmunity(int position) {
Helpers.removeFromList(mDamageImmunities, position);
}
public void replaceDamageImmunity(String oldDamageType, String newDamageType) {
Helpers.replaceItemInList(mDamageImmunities, oldDamageType, newDamageType, String::compareToIgnoreCase);
}
public LiveData<List<String>> getDamageResistances() { public LiveData<List<String>> getDamageResistances() {
return mDamageResistances; return mDamageResistances;
} }
public List<String> getDamageResistancesArray() {
return mDamageResistances.getValue();
}
public String addNewDamageResistance() {
return Helpers.addStringToList("", mDamageResistances);
}
public void removeDamageResistance(int position) {
Helpers.removeFromList(mDamageResistances, position);
}
public void replaceDamageResistance(String oldDamageType, String newDamageType) {
Helpers.replaceItemInList(mDamageResistances, oldDamageType, newDamageType, String::compareToIgnoreCase);
}
public LiveData<List<String>> getDamageVulnerabilities() {
return mDamageVulnerabilities;
}
public List<String> getDamageVulnerabilitiesArray() {
return mDamageVulnerabilities.getValue();
}
public String addNewDamageVulnerability() {
return Helpers.addStringToList("", mDamageVulnerabilities);
}
public void removeDamageVulnerability(int position) {
Helpers.removeFromList(mDamageVulnerabilities, position);
}
public void replaceDamageVulnerability(String oldDamageType, String newDamageType) {
Helpers.replaceItemInList(mDamageVulnerabilities, oldDamageType, newDamageType, String::compareToIgnoreCase);
}
public LiveData<List<String>> getConditionImmunities() {
return mConditionImmunities;
}
public List<String> getConditionImmunitiesArray() {
return mConditionImmunities.getValue();
}
public String addNewConditionImmunity() {
return Helpers.addStringToList("", mConditionImmunities);
}
public void removeConditionImmunity(int position) {
Helpers.removeFromList(mConditionImmunities, position);
}
public void replaceConditionImmunity(String oldDamageType, String newDamageType) {
Helpers.replaceItemInList(mConditionImmunities, oldDamageType, newDamageType, String::compareToIgnoreCase);
}
public LiveData<List<Language>> getLanguages() { public LiveData<List<Language>> getLanguages() {
return mLanguages; return mLanguages;
} }
@@ -787,7 +1002,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return monster; return monster;
} }
public LiveData<List<Trait>> getTraits(@NonNull TraitType type) { public LiveData<List<Trait>> getTraits(TraitType type) {
switch (type) { switch (type) {
case ABILITY: case ABILITY:
return mAbilities; return mAbilities;
@@ -807,7 +1022,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public void removeTrait(@NonNull TraitType type, int position) { public void removeTrait(TraitType type, int position) {
switch (type) { switch (type) {
case ABILITY: case ABILITY:
Helpers.removeFromList(mAbilities, position); Helpers.removeFromList(mAbilities, position);
@@ -833,7 +1048,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public void replaceTrait(@NonNull TraitType type, Trait oldTrait, Trait newTrait) { public void replaceTrait(TraitType type, Trait oldTrait, Trait newTrait) {
switch (type) { switch (type) {
case ABILITY: case ABILITY:
Helpers.replaceItemInList(mAbilities, oldTrait, newTrait); Helpers.replaceItemInList(mAbilities, oldTrait, newTrait);
@@ -858,7 +1073,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public Trait addNewTrait(@NonNull TraitType type) { public Trait addNewTrait(TraitType type) {
Trait newAction = new Trait("", ""); Trait newAction = new Trait("", "");
switch (type) { switch (type) {
case ABILITY: case ABILITY:
@@ -879,7 +1094,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public LiveData<List<String>> getStrings(@NonNull StringType type) { public LiveData<List<String>> getStrings(StringType type) {
switch (type) { switch (type) {
case CONDITION_IMMUNITY: case CONDITION_IMMUNITY:
return mConditionImmunities; return mConditionImmunities;
@@ -897,7 +1112,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public void removeString(@NonNull StringType type, int position) { public void removeString(StringType type, int position) {
switch (type) { switch (type) {
case CONDITION_IMMUNITY: case CONDITION_IMMUNITY:
Helpers.removeFromList(mConditionImmunities, position); Helpers.removeFromList(mConditionImmunities, position);
@@ -920,7 +1135,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public String addNewString(@NonNull StringType type) { public String addNewString(StringType type) {
String newString = ""; String newString = "";
switch (type) { switch (type) {
case CONDITION_IMMUNITY: case CONDITION_IMMUNITY:
@@ -939,7 +1154,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public void replaceString(@NonNull StringType type, String oldValue, String newValue) { public void replaceString(StringType type, String oldValue, String newValue) {
switch (type) { switch (type) {
case CONDITION_IMMUNITY: case CONDITION_IMMUNITY:
Helpers.replaceItemInList(mConditionImmunities, oldValue, newValue); Helpers.replaceItemInList(mConditionImmunities, oldValue, newValue);
@@ -961,7 +1176,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
} }
} }
public boolean moveTrait(@NonNull TraitType type, int from, int to) { public boolean moveTrait(TraitType type, int from, int to) {
switch (type) { switch (type) {
case ABILITY: case ABILITY:
return Helpers.moveItemInList(mAbilities, from, to); return Helpers.moveItemInList(mAbilities, from, to);
@@ -989,7 +1204,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return addItemToList(listData, newItem, null); return addItemToList(listData, newItem, null);
} }
static <T> T addItemToList(@NonNull MutableLiveData<List<T>> listData, T newItem, Comparator<? super T> comparator) { static <T> T addItemToList(MutableLiveData<List<T>> listData, T newItem, Comparator<? super T> comparator) {
ArrayList<T> newList = new ArrayList<>(listData.getValue()); ArrayList<T> newList = new ArrayList<>(listData.getValue());
newList.add(newItem); newList.add(newItem);
if (comparator != null) { if (comparator != null) {
@@ -999,14 +1214,14 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return newItem; return newItem;
} }
static <T> void removeFromList(@NonNull MutableLiveData<List<T>> listData, int position) { static <T> void removeFromList(MutableLiveData<List<T>> listData, int position) {
List<T> oldList = listData.getValue(); List<T> oldList = listData.getValue();
ArrayList<T> newList = new ArrayList<>(oldList); ArrayList<T> newList = new ArrayList<>(oldList);
newList.remove(position); newList.remove(position);
listData.setValue(newList); listData.setValue(newList);
} }
static <T> void replaceItemInList(@NonNull MutableLiveData<List<T>> listData, int position, T newItem, Comparator<? super T> comparator) { static <T> void replaceItemInList(MutableLiveData<List<T>> listData, int position, T newItem, Comparator<? super T> comparator) {
List<T> oldList = listData.getValue(); List<T> oldList = listData.getValue();
if (oldList == null) { if (oldList == null) {
oldList = new ArrayList<>(); oldList = new ArrayList<>();
@@ -1036,7 +1251,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
replaceItemInList(listData, position, newItem, null); replaceItemInList(listData, position, newItem, null);
} }
static <T> void replaceItemInList(@NonNull MutableLiveData<List<T>> listData, T oldItem, T newItem, Comparator<? super T> comparator) { static <T> void replaceItemInList(MutableLiveData<List<T>> listData, T oldItem, T newItem, Comparator<? super T> comparator) {
List<T> oldList = listData.getValue(); List<T> oldList = listData.getValue();
if (oldList == null) { if (oldList == null) {
oldList = new ArrayList<>(); oldList = new ArrayList<>();
@@ -1071,7 +1286,7 @@ public class EditMonsterViewModel extends ChangeTrackedViewModel {
return value; return value;
} }
static <T> boolean moveItemInList(@NonNull ChangeTrackedLiveData<List<T>> listData, int from, int to) { static <T> boolean moveItemInList(ChangeTrackedLiveData<List<T>> listData, int from, int to) {
List<T> oldList = listData.getValue(); List<T> oldList = listData.getValue();
if (oldList == null) { if (oldList == null) {
oldList = new ArrayList<>(); oldList = new ArrayList<>();

View File

@@ -5,7 +5,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
@@ -16,18 +16,19 @@ import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker; import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.MCFragment;
public class EditSavingThrowsFragment extends MCFragment { public class EditSavingThrowsFragment extends Fragment {
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mViewHolder; private ViewHolder mViewHolder;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_saving_throws, container, false); View root = inflater.inflate(R.layout.fragment_edit_saving_throws, container, false);
mViewHolder = new ViewHolder(root); mViewHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_saving_throws));
mViewHolder.strengthProficiency.setValue(mViewModel.getStrengthProficiency().getValue()); mViewHolder.strengthProficiency.setValue(mViewModel.getStrengthProficiency().getValue());
mViewHolder.strengthProficiency.setOnValueChangedListener(value -> mViewModel.setStrengthProficiency(value)); mViewHolder.strengthProficiency.setOnValueChangedListener(value -> mViewModel.setStrengthProficiency(value));
@@ -76,7 +77,7 @@ public class EditSavingThrowsFragment extends MCFragment {
AdvantagePicker charismaAdvantage; AdvantagePicker charismaAdvantage;
ProficiencyPicker charismaProficiency; ProficiencyPicker charismaProficiency;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
strengthAdvantage = root.findViewById(R.id.strengthAdvantage); strengthAdvantage = root.findViewById(R.id.strengthAdvantage);
strengthProficiency = root.findViewById(R.id.strengthProficiency); strengthProficiency = root.findViewById(R.id.strengthProficiency);
dexterityAdvantage = root.findViewById(R.id.dexterityAdvantage); dexterityAdvantage = root.findViewById(R.id.dexterityAdvantage);

View File

@@ -51,6 +51,7 @@ public class EditSkillFragment extends MCFragment {
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_skill, container, false); View root = inflater.inflate(R.layout.fragment_edit_skill, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_skill));
mHolder.abilityScore.setValue(mViewModel.getAbilityScore().getValue()); mHolder.abilityScore.setValue(mViewModel.getAbilityScore().getValue());
mHolder.abilityScore.setOnValueChangedListener(value -> mViewModel.setAbilityScore(value)); mHolder.abilityScore.setOnValueChangedListener(value -> mViewModel.setAbilityScore(value));
@@ -89,7 +90,7 @@ public class EditSkillFragment extends MCFragment {
ProficiencyPicker proficiency; ProficiencyPicker proficiency;
EditText name; EditText name;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
abilityScore = root.findViewById(R.id.abilityScore); abilityScore = root.findViewById(R.id.abilityScore);
advantage = root.findViewById(R.id.advantage); advantage = root.findViewById(R.id.advantage);
proficiency = root.findViewById(R.id.proficiency); proficiency = root.findViewById(R.id.proficiency);

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.ui.editmonster; package com.majinnaibu.monstercards.ui.editmonster;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import com.majinnaibu.monstercards.data.enums.AbilityScore; import com.majinnaibu.monstercards.data.enums.AbilityScore;
@@ -22,16 +21,15 @@ public class EditSkillViewModel extends ChangeTrackedViewModel {
mAbilityScore = new ChangeTrackedLiveData<>(AbilityScore.STRENGTH, this::makeDirty); mAbilityScore = new ChangeTrackedLiveData<>(AbilityScore.STRENGTH, this::makeDirty);
mAdvantageType = new ChangeTrackedLiveData<>(AdvantageType.NONE, this::makeDirty); mAdvantageType = new ChangeTrackedLiveData<>(AdvantageType.NONE, this::makeDirty);
mProficiencyType = new ChangeTrackedLiveData<>(ProficiencyType.NONE, this::makeDirty); mProficiencyType = new ChangeTrackedLiveData<>(ProficiencyType.NONE, this::makeDirty);
mName = new ChangeTrackedLiveData<>("New Skill", this::makeDirty); mName = new ChangeTrackedLiveData<>("Unknown Skill", this::makeDirty);
mSkill = new ChangeTrackedLiveData<>(makeSkill(), this::makeDirty); mSkill = new ChangeTrackedLiveData<>(makeSkill(), this::makeDirty);
} }
public void copyFromSkill(@NonNull Skill skill) { public void copyFromSkill(Skill skill) {
mAbilityScore.resetValue(skill.abilityScore); mAbilityScore.resetValue(skill.abilityScore);
mAdvantageType.resetValue(skill.advantageType); mAdvantageType.resetValue(skill.advantageType);
mProficiencyType.resetValue(skill.proficiencyType); mProficiencyType.resetValue(skill.proficiencyType);
mName.resetValue(skill.name); mName.resetValue(skill.name);
makeClean();
} }
public LiveData<Skill> getSkill() { public LiveData<Skill> getSkill() {
@@ -74,7 +72,6 @@ public class EditSkillViewModel extends ChangeTrackedViewModel {
mSkill.setValue(makeSkill()); mSkill.setValue(makeSkill());
} }
@NonNull
private Skill makeSkill() { private Skill makeSkill() {
return new Skill(mName.getValue(), mAbilityScore.getValue(), mAdvantageType.getValue(), mProficiencyType.getValue()); return new Skill(mName.getValue(), mAbilityScore.getValue(), mAdvantageType.getValue(), mProficiencyType.getValue());
} }

View File

@@ -31,19 +31,22 @@ public class EditSkillsFragment extends MCFragment {
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
private void navigateToEditSkill(@NonNull Skill skill) { private void navigateToEditSkill(Skill skill) {
NavDirections action = EditSkillsFragmentDirections.actionEditSkillsFragmentToEditSkillFragment(skill.name, skill.abilityScore, skill.proficiencyType, skill.advantageType); NavDirections action = EditSkillsFragmentDirections.actionEditSkillsFragmentToEditSkillFragment(skill.name, skill.abilityScore, skill.proficiencyType, skill.advantageType);
Navigation.findNavController(requireView()).navigate(action); View view = getView();
assert view != null;
Navigation.findNavController(view).navigate(action);
} }
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_skills_list, container, false); View root = inflater.inflate(R.layout.fragment_edit_skills_list, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_skills));
setupRecyclerView(mHolder.list); setupRecyclerView(mHolder.list);
setupAddSkillButton(mHolder.addSkill); setupAddSkillButton(mHolder.addSkill);
@@ -84,7 +87,7 @@ public class EditSkillsFragment extends MCFragment {
RecyclerView list; RecyclerView list;
FloatingActionButton addSkill; FloatingActionButton addSkill;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
this.list = root.findViewById(R.id.list); this.list = root.findViewById(R.id.list);
this.addSkill = root.findViewById(R.id.add_skill); this.addSkill = root.findViewById(R.id.add_skill);
} }

View File

@@ -7,8 +7,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.databinding.FragmentEditSkillsListItemBinding;
import com.majinnaibu.monstercards.models.Skill; import com.majinnaibu.monstercards.models.Skill;
import com.majinnaibu.monstercards.utils.ItemCallback;
import java.util.List; import java.util.List;
@@ -17,9 +17,9 @@ import java.util.List;
*/ */
public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkillsRecyclerViewAdapter.ViewHolder> { public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkillsRecyclerViewAdapter.ViewHolder> {
private final List<Skill> mValues; private final List<Skill> mValues;
private final ItemCallback mOnClick; private final ItemCallback<Skill> mOnClick;
public EditSkillsRecyclerViewAdapter(List<Skill> items, ItemCallback onClick) { public EditSkillsRecyclerViewAdapter(List<Skill> items, ItemCallback<Skill> onClick) {
mValues = items; mValues = items;
mOnClick = onClick; mOnClick = onClick;
} }
@@ -27,7 +27,7 @@ public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkil
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(FragmentEditSkillsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); return new ViewHolder(com.majinnaibu.monstercards.databinding.SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
} }
@Override @Override
@@ -36,7 +36,7 @@ public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkil
holder.mContentView.setText(mValues.get(position).name); holder.mContentView.setText(mValues.get(position).name);
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) { if (mOnClick != null) {
mOnClick.onItemCallback(holder.mItem); mOnClick.onItem(holder.mItem);
} }
}); });
} }
@@ -46,15 +46,11 @@ public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkil
return mValues.size(); return mValues.size();
} }
public interface ItemCallback {
void onItemCallback(Skill skill);
}
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
public final TextView mContentView; public final TextView mContentView;
public Skill mItem; public Skill mItem;
public ViewHolder(@NonNull FragmentEditSkillsListItemBinding binding) { public ViewHolder(@NonNull com.majinnaibu.monstercards.databinding.SimpleListItemBinding binding) {
super(binding.getRoot()); super(binding.getRoot());
mContentView = binding.content; mContentView = binding.content;
} }

View File

@@ -6,8 +6,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.SwitchCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
@@ -18,18 +18,19 @@ import com.majinnaibu.monstercards.ui.components.Stepper;
import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.TextChangedListener; import com.majinnaibu.monstercards.utils.TextChangedListener;
public class EditSpeedFragment extends MCFragment { public class EditSpeedFragment extends Fragment {
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
View root = inflater.inflate(R.layout.fragment_edit_speed, container, false); View root = inflater.inflate(R.layout.fragment_edit_speed, container, false);
mHolder = new ViewHolder(root); mHolder = new ViewHolder(root);
setTitle(getString(R.string.title_edit_speed));
mHolder.baseSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWalkSpeed(newValue)); mHolder.baseSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWalkSpeed(newValue));
mHolder.baseSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value)); mHolder.baseSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
@@ -75,7 +76,7 @@ public class EditSpeedFragment extends MCFragment {
final SwitchCompat hasCustomSpeed; final SwitchCompat hasCustomSpeed;
final EditText customSpeed; final EditText customSpeed;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
baseSpeed = root.findViewById(R.id.baseSpeed); baseSpeed = root.findViewById(R.id.baseSpeed);
burrowSpeed = root.findViewById(R.id.burrowSpeed); burrowSpeed = root.findViewById(R.id.burrowSpeed);
climbSpeed = root.findViewById(R.id.climbSpeed); climbSpeed = root.findViewById(R.id.climbSpeed);

View File

@@ -28,7 +28,7 @@ public class EditStringFragment extends MCFragment {
private StringType mStringType; private StringType mStringType;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
mViewModel = new ViewModelProvider(this).get(EditStringViewModel.class); mViewModel = new ViewModelProvider(this).get(EditStringViewModel.class);
if (getArguments() != null) { if (getArguments() != null) {
EditStringFragmentArgs args = EditStringFragmentArgs.fromBundle(getArguments()); EditStringFragmentArgs args = EditStringFragmentArgs.fromBundle(getArguments());
@@ -43,6 +43,7 @@ public class EditStringFragment extends MCFragment {
} }
@Nullable @Nullable
@org.jetbrains.annotations.Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
@@ -68,21 +69,20 @@ public class EditStringFragment extends MCFragment {
return root; return root;
} }
@NonNull private String getTitleForStringType(StringType type) {
private String getTitleForStringType(@NonNull StringType type) {
switch (type) { switch (type) {
case CONDITION_IMMUNITY: case CONDITION_IMMUNITY:
return getString(R.string.title_editConditionImmunity); return getString(R.string.title_edit_condition_immunity);
case DAMAGE_IMMUNITY: case DAMAGE_IMMUNITY:
return getString(R.string.title_editDamageImmunity); return getString(R.string.title_edit_damage_immunity);
case DAMAGE_RESISTANCE: case DAMAGE_RESISTANCE:
return getString(R.string.title_editDamageResistance); return getString(R.string.title_edit_damage_resistance);
case DAMAGE_VULNERABILITY: case DAMAGE_VULNERABILITY:
return getString(R.string.title_editDamageVulnerability); return getString(R.string.title_edit_damage_vulnerability);
case SENSE: case SENSE:
return getString(R.string.title_editSense); return getString(R.string.title_edit_sense);
default: default:
return getString(R.string.title_editString); return "";
} }
} }
@@ -95,7 +95,7 @@ public class EditStringFragment extends MCFragment {
private static class ViewHolder { private static class ViewHolder {
EditText description; EditText description;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
description = root.findViewById(R.id.description); description = root.findViewById(R.id.description);
} }
} }

View File

@@ -8,7 +8,11 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
<<<<<<<< HEAD:Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditConditionImmunitiesFragment.java
import androidx.fragment.app.Fragment;
========
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
>>>>>>>> f924bdd (Replaces condition immunities, damage immunities, damage resistances, damage vulnerabilities, and senses with a unified list of strings editor.):Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController; import androidx.navigation.NavController;
@@ -21,20 +25,39 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.R;
<<<<<<<< HEAD:Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditConditionImmunitiesFragment.java
========
import com.majinnaibu.monstercards.data.enums.StringType; import com.majinnaibu.monstercards.data.enums.StringType;
import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.MCFragment;
>>>>>>>> f924bdd (Replaces condition immunities, damage immunities, damage resistances, damage vulnerabilities, and senses with a unified list of strings editor.):Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback; import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.Logger;
import org.jetbrains.annotations.NotNull;
<<<<<<<< HEAD:Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditConditionImmunitiesFragment.java
/**
* A fragment representing a list of Items.
*/
public class EditConditionImmunitiesFragment extends Fragment {
========
import java.util.List; import java.util.List;
public class EditStringsFragment extends MCFragment { public class EditStringsFragment extends MCFragment {
>>>>>>>> f924bdd (Replaces condition immunities, damage immunities, damage resistances, damage vulnerabilities, and senses with a unified list of strings editor.):Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java
private EditMonsterViewModel mViewModel; private EditMonsterViewModel mViewModel;
private ViewHolder mHolder; private ViewHolder mHolder;
private StringType mStringType; private StringType mStringType;
<<<<<<<< HEAD:Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditConditionImmunitiesFragment.java
private void navigateToEditConditionImmunity(String condition) {
NavDirections action = EditConditionImmunitiesFragmentDirections.actionEditConditionImmunitiesFragmentToEditConditionImmunity(condition);
View view = getView();
assert view != null;
Navigation.findNavController(view).navigate(action);
========
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
Bundle arguments = getArguments(); Bundle arguments = getArguments();
if (arguments != null) { if (arguments != null) {
EditStringsFragmentArgs args = EditStringsFragmentArgs.fromBundle(arguments); EditStringsFragmentArgs args = EditStringsFragmentArgs.fromBundle(arguments);
@@ -43,11 +66,13 @@ public class EditStringsFragment extends MCFragment {
Logger.logWTF("EditStringsFragment needs arguments"); Logger.logWTF("EditStringsFragment needs arguments");
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
>>>>>>>> f924bdd (Replaces condition immunities, damage immunities, damage resistances, damage vulnerabilities, and senses with a unified list of strings editor.):Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditStringsFragment.java
} }
@Nullable @Nullable
@org.jetbrains.annotations.Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull @NotNull LayoutInflater inflater, @Nullable @org.jetbrains.annotations.Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
@@ -59,21 +84,20 @@ public class EditStringsFragment extends MCFragment {
return root; return root;
} }
@NonNull
private String getTitleForStringType(StringType type) { private String getTitleForStringType(StringType type) {
switch (type) { switch (type) {
case CONDITION_IMMUNITY: case CONDITION_IMMUNITY:
return getString(R.string.title_editConditionImmunities); return getString(R.string.title_edit_condition_immunities);
case DAMAGE_IMMUNITY: case DAMAGE_IMMUNITY:
return getString(R.string.title_editDamageImmunities); return getString(R.string.title_edit_damage_immunities);
case DAMAGE_RESISTANCE: case DAMAGE_RESISTANCE:
return getString(R.string.title_editDamageResistances); return getString(R.string.title_edit_damage_resistances);
case DAMAGE_VULNERABILITY: case DAMAGE_VULNERABILITY:
return getString(R.string.title_editDamageVulnerabilities); return getString(R.string.title_edit_damage_vulnerabilities);
case SENSE: case SENSE:
return getString(R.string.title_editSenses); return getString(R.string.title_edit_senses);
default: default:
return getString(R.string.title_editStrings); return "";
} }
} }
@@ -121,7 +145,7 @@ public class EditStringsFragment extends MCFragment {
RecyclerView list; RecyclerView list;
FloatingActionButton addItem; FloatingActionButton addItem;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
list = root.findViewById(R.id.list); list = root.findViewById(R.id.list);
addItem = root.findViewById(R.id.add_item); addItem = root.findViewById(R.id.add_item);
} }

View File

@@ -7,15 +7,16 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.databinding.FragmentEditStringsListItemBinding; import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
import com.majinnaibu.monstercards.utils.ItemCallback;
import java.util.List; import java.util.List;
public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStringsRecyclerViewAdapter.ViewHolder> { public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStringsRecyclerViewAdapter.ViewHolder> {
private final List<String> mValues; private final List<String> mValues;
private final ItemCallback mOnClick; private final ItemCallback<String> mOnClick;
public EditStringsRecyclerViewAdapter(List<String> items, ItemCallback onClick) { public EditStringsRecyclerViewAdapter(List<String> items, ItemCallback<String> onClick) {
mValues = items; mValues = items;
mOnClick = onClick; mOnClick = onClick;
} }
@@ -23,7 +24,7 @@ public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStr
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(FragmentEditStringsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); return new ViewHolder(SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
} }
@Override @Override
@@ -32,7 +33,7 @@ public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStr
holder.mContentView.setText(mValues.get(position)); holder.mContentView.setText(mValues.get(position));
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) { if (mOnClick != null) {
mOnClick.onItemCallback(holder.mItem); mOnClick.onItem(holder.mItem);
} }
}); });
} }
@@ -42,15 +43,11 @@ public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStr
return mValues.size(); return mValues.size();
} }
public interface ItemCallback {
void onItemCallback(String value);
}
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
public final TextView mContentView; public final TextView mContentView;
public String mItem; public String mItem;
public ViewHolder(@NonNull FragmentEditStringsListItemBinding binding) { public ViewHolder(@NonNull SimpleListItemBinding binding) {
super(binding.getRoot()); super(binding.getRoot());
mContentView = binding.content; mContentView = binding.content;
} }

View File

@@ -29,7 +29,7 @@ public class EditTraitFragment extends MCFragment {
private TraitType mTraitType; private TraitType mTraitType;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
mViewModel = new ViewModelProvider(this).get(EditTraitViewModel.class); mViewModel = new ViewModelProvider(this).get(EditTraitViewModel.class);
if (getArguments() != null) { if (getArguments() != null) {
EditTraitFragmentArgs args = EditTraitFragmentArgs.fromBundle(getArguments()); EditTraitFragmentArgs args = EditTraitFragmentArgs.fromBundle(getArguments());
@@ -44,7 +44,7 @@ public class EditTraitFragment extends MCFragment {
} }
@Nullable @Nullable
@org.jetbrains.annotations.Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
@@ -82,19 +82,19 @@ public class EditTraitFragment extends MCFragment {
private String getTitleForTraitType(TraitType type) { private String getTitleForTraitType(TraitType type) {
switch (type) { switch (type) {
case ABILITY: case ABILITY:
return getString(R.string.title_editAbility); return getString(R.string.title_edit_ability);
case ACTION: case ACTION:
return getString(R.string.title_editAction); return getString(R.string.title_edit_action);
case LAIR_ACTION: case LAIR_ACTION:
return getString(R.string.title_editLairAction); return getString(R.string.title_edit_lair_action);
case LEGENDARY_ACTION: case LEGENDARY_ACTION:
return getString(R.string.title_editLegendaryAction); return getString(R.string.title_edit_legendary_action);
case REACTIONS: case REACTIONS:
return getString(R.string.title_editReaction); return getString(R.string.title_edit_reaction);
case REGIONAL_ACTION: case REGIONAL_ACTION:
return getString(R.string.title_editRegionalAction); return getString(R.string.title_edit_regional_action);
default: default:
return getString(R.string.title_editTrait); return "";
} }
} }
@@ -102,7 +102,7 @@ public class EditTraitFragment extends MCFragment {
EditText description; EditText description;
EditText name; EditText name;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
description = root.findViewById(R.id.description); description = root.findViewById(R.id.description);
name = root.findViewById(R.id.name); name = root.findViewById(R.id.name);
} }

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.ui.editmonster; package com.majinnaibu.monstercards.ui.editmonster;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -50,13 +49,12 @@ public class EditTraitViewModel extends ChangeTrackedViewModel {
return mAbility.getValue(); return mAbility.getValue();
} }
public void copyFromTrait(@NonNull Trait trait) { public void copyFromTrait(Trait trait) {
makeClean(); makeClean();
mName.resetValue(trait.name); mName.resetValue(trait.name);
mDescription.resetValue(trait.description); mDescription.resetValue(trait.description);
} }
@NonNull
private Trait makeAbility() { private Trait makeAbility() {
return new Trait(mName.getValue(), mDescription.getValue()); return new Trait(mName.getValue(), mDescription.getValue());
} }

View File

@@ -35,7 +35,6 @@ public class EditTraitsFragment extends MCFragment {
private TraitType mTraitType; private TraitType mTraitType;
private EditTraitsRecyclerViewAdapter mAdapter; private EditTraitsRecyclerViewAdapter mAdapter;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
if (getArguments() != null) { if (getArguments() != null) {
@@ -47,9 +46,8 @@ public class EditTraitsFragment extends MCFragment {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment); NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class); mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);

View File

@@ -9,8 +9,9 @@ import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.databinding.FragmentEditTraitsListItemBinding; import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
import com.majinnaibu.monstercards.models.Trait; import com.majinnaibu.monstercards.models.Trait;
import com.majinnaibu.monstercards.utils.ItemCallback;
public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraitsRecyclerViewAdapter.ViewHolder> { public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraitsRecyclerViewAdapter.ViewHolder> {
private static final DiffUtil.ItemCallback<Trait> DIFF_CALLBACK = new DiffUtil.ItemCallback<Trait>() { private static final DiffUtil.ItemCallback<Trait> DIFF_CALLBACK = new DiffUtil.ItemCallback<Trait>() {
@@ -25,9 +26,9 @@ public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraits
return oldItem.equals(newItem); return oldItem.equals(newItem);
} }
}; };
private final ItemCallback mOnClick; private final ItemCallback<Trait> mOnClick;
protected EditTraitsRecyclerViewAdapter(ItemCallback onClick) { protected EditTraitsRecyclerViewAdapter(ItemCallback<Trait> onClick) {
super(DIFF_CALLBACK); super(DIFF_CALLBACK);
mOnClick = onClick; mOnClick = onClick;
} }
@@ -35,7 +36,7 @@ public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraits
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(FragmentEditTraitsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); return new ViewHolder(SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
} }
@Override @Override
@@ -44,20 +45,16 @@ public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraits
holder.mContentView.setText(holder.mItem.name); holder.mContentView.setText(holder.mItem.name);
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) { if (mOnClick != null) {
mOnClick.onItemCallback(holder.mItem); mOnClick.onItem(holder.mItem);
} }
}); });
} }
public interface ItemCallback {
void onItemCallback(Trait trait);
}
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
public final TextView mContentView; public final TextView mContentView;
public Trait mItem; public Trait mItem;
public ViewHolder(@NonNull FragmentEditTraitsListItemBinding binding) { public ViewHolder(@NonNull SimpleListItemBinding binding) {
super(binding.getRoot()); super(binding.getRoot());
mContentView = binding.content; mContentView = binding.content;
} }

View File

@@ -7,6 +7,8 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.DividerItemDecoration;
@@ -18,65 +20,52 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.R;
import com.majinnaibu.monstercards.data.MonsterRepository; import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.databinding.FragmentLibraryBinding;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback; import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.Logger;
import java.util.UUID; import java.util.List;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.observers.DisposableCompletableObserver; import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.schedulers.Schedulers;
public class LibraryFragment extends MCFragment { public class LibraryFragment extends MCFragment {
private LibraryViewModel mViewModel;
private ViewHolder mHolder;
private LibraryRecyclerViewAdapter mAdapter;
public View onCreateView(@NonNull LayoutInflater inflater, @Override
ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_library, container, false); mViewModel = new ViewModelProvider(this).get(LibraryViewModel.class);
FragmentLibraryBinding binding = FragmentLibraryBinding.inflate(inflater, container, false);
FloatingActionButton fab = root.findViewById(R.id.fab); mHolder = new ViewHolder(binding);
assert fab != null; // TODO: set the title with setTitle(...)
setupAddMonsterButton(fab); setupAddMonsterButton(mHolder.addButton);
setupMonsterList(mHolder.list);
final RecyclerView recyclerView = root.findViewById(R.id.monster_list); return binding.getRoot();
assert recyclerView != null;
setupRecyclerView(recyclerView);
return root;
} }
private void setupRecyclerView(@NonNull RecyclerView recyclerView) { private void setupMonsterList(@NonNull RecyclerView recyclerView) {
Context context = requireContext(); 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); LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager); recyclerView.setLayoutManager(layoutManager);
LiveData<List<Monster>> monsterData = mViewModel.getMonsters();
mAdapter = new LibraryRecyclerViewAdapter(this::navigateToMonsterDetail);
if (monsterData != null) {
monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters));
}
recyclerView.setAdapter(mAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation()); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration); recyclerView.addItemDecoration(dividerItemDecoration);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(requireContext(), (position, direction) -> adapter.deleteItem(position), null)); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(
requireContext(),
(position, direction) -> mViewModel.removeMonster(position),
null));
itemTouchHelper.attachToRecyclerView(recyclerView); itemTouchHelper.attachToRecyclerView(recyclerView);
} }
@@ -98,7 +87,7 @@ public class LibraryFragment extends MCFragment {
view, view,
getString(R.string.snackbar_monster_created, monster.name), getString(R.string.snackbar_monster_created, monster.name),
Snackbar.LENGTH_LONG) Snackbar.LENGTH_LONG)
.setAction("Action", (_view) -> navigateToMonsterDetail(monster.id)) .setAction("Action", (_view) -> navigateToMonsterDetail(monster))
.show(); .show();
} }
@@ -114,8 +103,22 @@ public class LibraryFragment extends MCFragment {
}); });
} }
protected void navigateToMonsterDetail(@NonNull UUID monsterId) { protected void navigateToMonsterDetail(Monster monster) {
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString()); if (monster != null) {
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monster.id.toString());
Navigation.findNavController(requireView()).navigate(action); Navigation.findNavController(requireView()).navigate(action);
} else {
Logger.logError("Can't navigate to MonsterDetail without a monster.");
}
}
private static class ViewHolder {
final FloatingActionButton addButton;
final RecyclerView list;
public ViewHolder(FragmentLibraryBinding binding) {
addButton = binding.fab;
list = binding.monsterList;
}
} }
} }

View File

@@ -1,115 +1,53 @@
package com.majinnaibu.monstercards.ui.library; package com.majinnaibu.monstercards.ui.library;
import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.SimpleListItemViewHolder;
import com.majinnaibu.monstercards.utils.ItemCallback;
import java.util.ArrayList; public class LibraryRecyclerViewAdapter extends ListAdapter<Monster, SimpleListItemViewHolder<Monster>> {
import java.util.List; private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
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 @Override
public void onClick(@NonNull View view) { public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
Monster monster = (Monster) view.getTag(); return Monster.areItemsTheSame(oldItem, newItem);
if (mOnClick != null) {
mOnClick.onItemCallback(monster);
} }
@Override
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
return Monster.areContentsTheSame(oldItem, newItem);
} }
}; };
private List<Monster> mValues; private final ItemCallback<Monster> mOnClick;
private Disposable mDisposable;
public LibraryRecyclerViewAdapter(Context context, public LibraryRecyclerViewAdapter(ItemCallback<Monster> onClick) {
Flowable<List<Monster>> itemsObservable, super(DIFF_CALLBACK);
ItemCallback onClick,
ItemCallback onDelete) {
mItemsObservable = itemsObservable;
mValues = new ArrayList<>();
mContext = context;
mOnDelete = onDelete;
mOnClick = onClick; mOnClick = onClick;
mDisposable = null;
} }
@Override @Override
@NonNull @NonNull
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public SimpleListItemViewHolder<Monster> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) SimpleListItemBinding binding = SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
.inflate(R.layout.monster_list_content, parent, false); return new SimpleListItemViewHolder<>(binding);
return new ViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(final @NonNull ViewHolder holder, int position) { public void onBindViewHolder(final @NonNull SimpleListItemViewHolder<Monster> holder, int position) {
Monster monster = mValues.get(position); Monster monster = getItem(position);
holder.mContentView.setText(monster.name); holder.item = monster;
holder.contentView.setText(monster.name);
holder.itemView.setTag(monster); holder.itemView.setTag(monster);
holder.itemView.setOnClickListener(mOnClickListener); holder.itemView.setOnClickListener(v -> {
if (mOnClick != null) {
mOnClick.onItem(holder.item);
} }
@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);
}
}
} }

View File

@@ -0,0 +1,74 @@
package com.majinnaibu.monstercards.ui.library;
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.majinnaibu.monstercards.AppDatabase;
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.annotations.NonNull;
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subscribers.DisposableSubscriber;
public class LibraryViewModel extends AndroidViewModel {
private final AppDatabase mDB;
private final MutableLiveData<List<Monster>> mMonsters;
public LibraryViewModel(Application application) {
super(application);
mDB = AppDatabase.getInstance(application);
mMonsters = new MutableLiveData<>(new ArrayList<>());
mDB.monsterDAO()
.getAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new DisposableSubscriber<List<Monster>>() {
@Override
public void onNext(List<Monster> monsters) {
mMonsters.setValue(monsters);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
}
public LiveData<List<Monster>> getMonsters() {
return mMonsters;
}
public void removeMonster(int position) {
Monster monster = mMonsters.getValue().get(position);
mDB.monsterDAO()
.delete(monster)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new DisposableCompletableObserver() {
@Override
public void onComplete() {
}
@Override
public void onError(@NonNull Throwable e) {
}
});
}
}

View File

@@ -13,7 +13,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
@@ -30,15 +29,13 @@ import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.ui.shared.MCFragment; import com.majinnaibu.monstercards.ui.shared.MCFragment;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.utils.Logger;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import io.reactivex.rxjava3.observers.DisposableSingleObserver; import io.reactivex.rxjava3.observers.DisposableSingleObserver;
public class MonsterDetailFragment extends MCFragment { public class MonsterDetailFragment extends MCFragment {
private ViewHolder mHolder;
private MonsterDetailViewModel mViewModel; private MonsterDetailViewModel monsterDetailViewModel;
public View onCreateView(@NonNull LayoutInflater inflater, public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) { ViewGroup container, Bundle savedInstanceState) {
@@ -47,13 +44,16 @@ public class MonsterDetailFragment extends MCFragment {
assert arguments != null; assert arguments != null;
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId()); UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
setHasOptionsMenu(true); setHasOptionsMenu(true);
mViewModel = new ViewModelProvider(this).get(MonsterDetailViewModel.class);
monsterDetailViewModel = new ViewModelProvider(this).get(MonsterDetailViewModel.class);
View root = inflater.inflate(R.layout.fragment_monster, container, false);
repository.getMonster(monsterId).toObservable() repository.getMonster(monsterId).toObservable()
.firstOrError() .firstOrError()
.subscribe(new DisposableSingleObserver<Monster>() { .subscribe(new DisposableSingleObserver<Monster>() {
@Override @Override
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) { public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
mViewModel.setMonster(monster); monsterDetailViewModel.setMonster(monster);
dispose(); dispose();
} }
@@ -63,70 +63,125 @@ public class MonsterDetailFragment extends MCFragment {
dispose(); dispose();
} }
}); });
View root = inflater.inflate(R.layout.fragment_monster, container, false);
mHolder = new ViewHolder(root);
mViewModel.getName().observe(getViewLifecycleOwner(), name -> { final TextView monsterName = root.findViewById(R.id.name);
mHolder.name.setText(name); monsterDetailViewModel.getName().observe(getViewLifecycleOwner(), monsterName::setText);
setTitle(getString(R.string.title_monsterDetails_fmt, name));
final TextView monsterMeta = root.findViewById(R.id.meta);
monsterDetailViewModel.getMeta().observe(getViewLifecycleOwner(), monsterMeta::setText);
final TextView monsterArmorClass = root.findViewById(R.id.armor_class);
monsterDetailViewModel.getArmorClass().observe(getViewLifecycleOwner(), armorText -> monsterArmorClass.setText(Html.fromHtml("<b>Armor Class</b> " + armorText)));
final TextView monsterHitPoints = root.findViewById(R.id.hit_points);
monsterDetailViewModel.getHitPoints().observe(getViewLifecycleOwner(), hitPoints -> monsterHitPoints.setText(Html.fromHtml("<b>Hit Points</b> " + hitPoints)));
final TextView monsterSpeed = root.findViewById(R.id.speed);
monsterDetailViewModel.getSpeed().observe(getViewLifecycleOwner(), speed -> monsterSpeed.setText(Html.fromHtml("<b>Speed</b> " + speed)));
final TextView monsterStrength = root.findViewById(R.id.strength);
monsterDetailViewModel.getStrength().observe(getViewLifecycleOwner(), monsterStrength::setText);
final TextView monsterDexterity = root.findViewById(R.id.dexterity);
monsterDetailViewModel.getDexterity().observe(getViewLifecycleOwner(), monsterDexterity::setText);
final TextView monsterConstitution = root.findViewById(R.id.constitution);
monsterDetailViewModel.getConstitution().observe(getViewLifecycleOwner(), monsterConstitution::setText);
final TextView monsterIntelligence = root.findViewById(R.id.intelligence);
monsterDetailViewModel.getIntelligence().observe(getViewLifecycleOwner(), monsterIntelligence::setText);
final TextView monsterWisdom = root.findViewById(R.id.wisdom);
monsterDetailViewModel.getWisdom().observe(getViewLifecycleOwner(), monsterWisdom::setText);
final TextView monsterCharisma = root.findViewById(R.id.charisma);
monsterDetailViewModel.getCharisma().observe(getViewLifecycleOwner(), monsterCharisma::setText);
final TextView monsterSavingThrows = root.findViewById(R.id.saving_throws);
monsterDetailViewModel.getSavingThrows().observe(getViewLifecycleOwner(), savingThrows -> {
if (StringHelper.isNullOrEmpty(savingThrows)) {
monsterSavingThrows.setVisibility(View.GONE);
} else {
monsterSavingThrows.setVisibility(View.VISIBLE);
}
monsterSavingThrows.setText(Html.fromHtml("<b>Saving Throws</b> " + savingThrows));
}); });
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; final TextView monsterSkills = root.findViewById(R.id.skills);
} monsterDetailViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> {
if (StringHelper.isNullOrEmpty(skills)) {
private void setupLabeledTextView(@NonNull TextView view, String text, int titleId) { monsterSkills.setVisibility(View.GONE);
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 { } else {
root.setVisibility(View.VISIBLE); monsterSkills.setVisibility(View.VISIBLE);
} }
Spanned formatted; monsterSkills.setText(Html.fromHtml("<b>Skills</b> " + skills));
if (StringHelper.isNullOrEmpty(title)) { });
formatted = Html.fromHtml(text);
final TextView monsterDamageVulnerabilities = root.findViewById(R.id.damage_vulnerabilities);
monsterDetailViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageType -> {
if (StringHelper.isNullOrEmpty(damageType)) {
monsterDamageVulnerabilities.setVisibility(View.GONE);
} else { } else {
formatted = Html.fromHtml(String.format("<b>%s</b> %s", title, text)); monsterDamageVulnerabilities.setVisibility(View.VISIBLE);
}
root.setText(formatted);
} }
monsterDamageVulnerabilities.setText(Html.fromHtml("<b>Damage Vulnerabilities</b> " + damageType));
});
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits) { final TextView monsterDamageResistances = root.findViewById(R.id.damage_resistances);
setupTraitList(root, traits, null, null); monsterDetailViewModel.getDamageResistances().observe(getViewLifecycleOwner(), damageType -> {
if (StringHelper.isNullOrEmpty(damageType)) {
monsterDamageResistances.setVisibility(View.GONE);
} else {
monsterDamageResistances.setVisibility(View.VISIBLE);
} }
monsterDamageResistances.setText(Html.fromHtml("<b>Damage Resistances</b> " + damageType));
});
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits, View label, View divider) { final TextView monsterDamageImmunities = root.findViewById(R.id.damage_immunities);
int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE; monsterDetailViewModel.getDamageImmunities().observe(getViewLifecycleOwner(), damageType -> {
if (StringHelper.isNullOrEmpty(damageType)) {
monsterDamageImmunities.setVisibility(View.GONE);
} else {
monsterDamageImmunities.setVisibility(View.VISIBLE);
}
monsterDamageImmunities.setText(Html.fromHtml("<b>Damage Immunities</b> " + damageType));
});
final TextView monsterConditionImmunities = root.findViewById(R.id.condition_immunities);
monsterDetailViewModel.getConditionImmunities().observe(getViewLifecycleOwner(), conditionImmunities -> {
if (StringHelper.isNullOrEmpty(conditionImmunities)) {
monsterConditionImmunities.setVisibility(View.GONE);
} else {
monsterConditionImmunities.setVisibility(View.VISIBLE);
}
monsterConditionImmunities.setText(Html.fromHtml("<b>Condition Immunities</b> " + conditionImmunities));
});
final TextView monsterSenses = root.findViewById(R.id.senses);
monsterDetailViewModel.getSenses().observe(getViewLifecycleOwner(), senses -> {
if (StringHelper.isNullOrEmpty(senses)) {
monsterSenses.setVisibility(View.GONE);
} else {
monsterSenses.setVisibility(View.VISIBLE);
}
monsterSenses.setText(Html.fromHtml("<b>Senses</b> " + senses));
});
final TextView monsterLanguages = root.findViewById(R.id.languages);
monsterDetailViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> {
if (StringHelper.isNullOrEmpty(languages)) {
monsterLanguages.setVisibility(View.GONE);
} else {
monsterLanguages.setVisibility(View.VISIBLE);
}
monsterLanguages.setText(Html.fromHtml("<b>Languages</b> " + languages));
});
final TextView monsterChallenge = root.findViewById(R.id.challenge);
monsterDetailViewModel.getChallenge().observe(getViewLifecycleOwner(), challengeRating -> monsterChallenge.setText(Html.fromHtml("<b>Challenge</b> " + challengeRating)));
final LinearLayout monsterAbilities = root.findViewById(R.id.abilities);
monsterDetailViewModel.getAbilities().observe(getViewLifecycleOwner(), abilities -> {
Context context = getContext(); Context context = getContext();
DisplayMetrics displayMetrics = null; DisplayMetrics displayMetrics = null;
if (context != null) { if (context != null) {
@@ -135,24 +190,48 @@ public class MonsterDetailFragment extends MCFragment {
displayMetrics = resources.getDisplayMetrics(); displayMetrics = resources.getDisplayMetrics();
} }
} }
root.removeAllViews(); monsterAbilities.removeAllViews();
for (String action : traits) { if (abilities != null) {
TextView tvAction = new TextView(getContext()); for (String ability : abilities) {
TextView tvAbility = new TextView(context);
// TODO: Handle multiline block quotes specially so they stay multiline. // 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 // 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
Spanned spannedText = Html.fromHtml(CommonMarkHelper.toHtml(ability));
tvAbility.setText(spannedText);
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);
tvAbility.setLayoutParams(layoutParams);
monsterAbilities.addView(tvAbility);
}
}
});
final LinearLayout monsterActions = root.findViewById(R.id.actions);
monsterDetailViewModel.getActions().observe(getViewLifecycleOwner(), actions -> {
Context context = getContext();
DisplayMetrics displayMetrics = null;
if (context != null) {
Resources resources = context.getResources();
if (resources != null) {
displayMetrics = resources.getDisplayMetrics();
}
}
monsterActions.removeAllViews();
if (actions != null) {
for (String action : actions) {
TextView tvAction = new TextView(getContext());
tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action))); tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 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); layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
tvAction.setLayoutParams(layoutParams); tvAction.setLayoutParams(layoutParams);
root.addView(tvAction); monsterActions.addView(tvAction);
} }
root.setVisibility(visibility);
if (label != null) {
label.setVisibility(visibility);
}
if (divider != null) {
divider.setVisibility(visibility);
} }
});
// TODO: add lair actions, legendary actions, reactions, and regional actions
return root;
} }
@Override @Override
@@ -164,10 +243,12 @@ public class MonsterDetailFragment extends MCFragment {
@Override @Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) { public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.menu_action_edit_monster) { if (item.getItemId() == R.id.menu_action_edit_monster) {
UUID monsterId = mViewModel.getId().getValue(); UUID monsterId = monsterDetailViewModel.getId().getValue();
if (monsterId != null) { if (monsterId != null) {
NavDirections action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString()); NavDirections action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
Navigation.findNavController(requireView()).navigate(action); View view = getView();
assert view != null;
Navigation.findNavController(view).navigate(action);
} else { } else {
Logger.logWTF("monsterId cannot be null."); Logger.logWTF("monsterId cannot be null.");
} }
@@ -175,82 +256,4 @@ public class MonsterDetailFragment extends MCFragment {
} }
return super.onOptionsItemSelected(item); 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);
}
}
} }

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.ui.monster; package com.majinnaibu.monstercards.ui.monster;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
@@ -181,7 +180,7 @@ public class MonsterDetailViewModel extends ViewModel {
return mMonsterId; return mMonsterId;
} }
public void setMonster(@NonNull Monster monster) { public void setMonster(Monster monster) {
mMonster = monster; mMonster = monster;
mAbilities.setValue(mMonster.getAbilityDescriptions()); mAbilities.setValue(mMonster.getAbilityDescriptions());
mActions.setValue(mMonster.getActionDescriptions()); mActions.setValue(mMonster.getActionDescriptions());

View File

@@ -88,7 +88,7 @@ public class MonsterImportFragment extends MCFragment {
return root; return root;
} }
private void setupLabeledTextView(@NonNull TextView view, String text, int titleId) { private void setupLabeledTextView(TextView view, String text, int titleId) {
String title = getString(titleId); String title = getString(titleId);
String fullText = String.format("<b>%s</b> %s", title, text); String fullText = String.format("<b>%s</b> %s", title, text);
view.setText(Html.fromHtml(fullText)); view.setText(Html.fromHtml(fullText));
@@ -184,7 +184,7 @@ public class MonsterImportFragment extends MCFragment {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
private void navigateToEditMonster(@NonNull UUID monsterId) { private void navigateToEditMonster(UUID monsterId) {
NavController navController = Navigation.findNavController(requireView()); NavController navController = Navigation.findNavController(requireView());
NavDirections action; NavDirections action;
action = MonsterImportFragmentDirections.actionMonsterImportFragmentToNavigationLibrary(); action = MonsterImportFragmentDirections.actionMonsterImportFragmentToNavigationLibrary();
@@ -234,7 +234,7 @@ public class MonsterImportFragment extends MCFragment {
final TextView regionalEffects_label; final TextView regionalEffects_label;
final ImageView regionalEffects_divider; final ImageView regionalEffects_divider;
ViewHolder(@NonNull View root) { ViewHolder(View root) {
this.root = root; this.root = root;
name = root.findViewById(R.id.name); name = root.findViewById(R.id.name);
meta = root.findViewById(R.id.meta); meta = root.findViewById(R.id.meta);

View File

@@ -1,6 +1,5 @@
package com.majinnaibu.monstercards.ui.monster; package com.majinnaibu.monstercards.ui.monster;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
@@ -184,7 +183,7 @@ public class MonsterImportViewModel extends ViewModel {
return mMonster; return mMonster;
} }
public void setMonster(@NonNull Monster monster) { public void setMonster(Monster monster) {
mMonster = monster; mMonster = monster;
mAbilities.setValue(mMonster.getAbilityDescriptions()); mAbilities.setValue(mMonster.getAbilityDescriptions());
mActions.setValue(mMonster.getActionDescriptions()); mActions.setValue(mMonster.getActionDescriptions());

View File

@@ -1,34 +1,49 @@
package com.majinnaibu.monstercards.ui.search; package com.majinnaibu.monstercards.ui.search;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.databinding.FragmentSearchBinding;
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.MCFragment;
import com.majinnaibu.monstercards.utils.Logger;
import java.util.List;
public class SearchFragment extends MCFragment { public class SearchFragment extends MCFragment {
private SearchViewModel mViewModel;
private ViewHolder mHolder;
private SearchResultsRecyclerViewAdapter mAdapter;
public View onCreateView(@NonNull LayoutInflater inflater, @Override
ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_search, container, false); mViewModel = new ViewModelProvider(this).get(SearchViewModel.class);
MonsterRepository repository = this.getMonsterRepository(); FragmentSearchBinding binding = FragmentSearchBinding.inflate(inflater, container, false);
SearchResultsRecyclerViewAdapter adapter = new SearchResultsRecyclerViewAdapter(repository, null); mHolder = new ViewHolder(binding);
final RecyclerView recyclerView = root.findViewById(R.id.monster_list); // TODO: set the title with setTitle(...)
assert recyclerView != null; setupMonsterList(binding.monsterList);
setupRecyclerView(recyclerView, adapter); setupFilterBox(binding.searchQuery);
return binding.getRoot();
}
final TextView textView = root.findViewById(R.id.search_query); private void setupFilterBox(@NonNull TextView textBox) {
textView.addTextChangedListener(new TextWatcher() { textBox.addTextChangedListener(new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
} }
@@ -39,15 +54,42 @@ public class SearchFragment extends MCFragment {
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
adapter.doSearch(textView.getText().toString()); mViewModel.setFilterText(textBox.getText().toString());
} }
}); });
return root;
} }
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @NonNull SearchResultsRecyclerViewAdapter adapter) { private void setupMonsterList(@NonNull RecyclerView recyclerView) {
recyclerView.setAdapter(adapter); Context context = requireContext();
recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); LinearLayoutManager layoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(layoutManager);
LiveData<List<Monster>> monsterData = mViewModel.getMatchedMonsters();
mAdapter = new SearchResultsRecyclerViewAdapter(this::navigateToMonsterDetail);
if (monsterData != null) {
monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters));
}
recyclerView.setAdapter(mAdapter);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
recyclerView.addItemDecoration(dividerItemDecoration);
}
public void navigateToMonsterDetail(Monster monster) {
if (monster == null) {
NavDirections action = SearchFragmentDirections.actionNavigationSearchToNavigationMonster(monster.id.toString());
Navigation.findNavController(requireView()).navigate(action);
} else {
Logger.logError("Can't navigate to MonsterDetail without a monster.");
}
}
private static class ViewHolder {
final RecyclerView monsterList;
final EditText filterQuery;
public ViewHolder(FragmentSearchBinding binding) {
monsterList = binding.monsterList;
filterQuery = binding.searchQuery;
}
} }
} }

View File

@@ -1,90 +1,52 @@
package com.majinnaibu.monstercards.ui.search; package com.majinnaibu.monstercards.ui.search;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import com.majinnaibu.monstercards.R; import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
import com.majinnaibu.monstercards.data.MonsterRepository;
import com.majinnaibu.monstercards.models.Monster; import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.utils.Logger; import com.majinnaibu.monstercards.ui.shared.SimpleListItemViewHolder;
import com.majinnaibu.monstercards.utils.ItemCallback;
import java.util.ArrayList; public class SearchResultsRecyclerViewAdapter extends ListAdapter<Monster, SimpleListItemViewHolder<Monster>> {
import java.util.List; private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
@Override
import io.reactivex.rxjava3.core.Flowable; public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
import io.reactivex.rxjava3.disposables.Disposable; return Monster.areItemsTheSame(oldItem, newItem);
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) { @Override
if (mSubscriptionHandler != null && !mSubscriptionHandler.isDisposed()) { public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
mSubscriptionHandler.dispose(); return Monster.areContentsTheSame(oldItem, newItem);
} }
mSearchText = searchText; };
Flowable<List<Monster>> foundMonsters = mRepository.searchMonsters(mSearchText); private final ItemCallback<Monster> mOnClick;
mSubscriptionHandler = foundMonsters.subscribe(monsters -> {
mValues = monsters; public SearchResultsRecyclerViewAdapter(ItemCallback<Monster> onClick) {
notifyDataSetChanged(); super(DIFF_CALLBACK);
}, mOnClick = onClick;
throwable -> Logger.logError("Error performing search", throwable));
} }
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public SimpleListItemViewHolder<Monster> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) SimpleListItemBinding binding = SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
.inflate(R.layout.monster_list_content, parent, false); return new SimpleListItemViewHolder<>(binding);
return new ViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { public void onBindViewHolder(@NonNull final SimpleListItemViewHolder<Monster> holder, int position) {
Monster monster = mValues.get(position); Monster monster = getItem(position);
holder.mContentView.setText(monster.name); holder.item = monster;
holder.itemView.setTag(monster); holder.contentView.setText(monster.name);
holder.itemView.setOnClickListener(view -> { holder.itemView.setOnClickListener(view -> {
if (mOnClickHandler != null) { if (mOnClick != null) {
mOnClickHandler.onItem(monster); mOnClick.onItem(holder.item);
} }
}); });
} }
@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);
}
}
} }

View File

@@ -0,0 +1,118 @@
package com.majinnaibu.monstercards.ui.search;
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import com.majinnaibu.monstercards.AppDatabase;
import com.majinnaibu.monstercards.helpers.StringHelper;
import com.majinnaibu.monstercards.models.Monster;
import com.majinnaibu.monstercards.utils.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.subscribers.DisposableSubscriber;
public class SearchViewModel extends AndroidViewModel {
private final MutableLiveData<List<Monster>> mAllMonsters;
private final MediatorLiveData<List<Monster>> mFilteredMonsters;
private final MutableLiveData<String> mFilterText;
private final AppDatabase mDB;
public SearchViewModel(Application application) {
super(application);
mDB = AppDatabase.getInstance(application);
mAllMonsters = new MutableLiveData<>(new ArrayList<>());
mFilterText = new MutableLiveData<>("");
mFilteredMonsters = new MediatorLiveData<>();
mFilteredMonsters.addSource(
mAllMonsters,
allMonsters -> mFilteredMonsters.setValue(
filterMonsters(allMonsters, mFilterText.getValue())));
mFilteredMonsters.addSource(
mFilterText,
filterText -> mFilteredMonsters.setValue(
filterMonsters(mAllMonsters.getValue(), filterText)));
mDB.monsterDAO()
.getAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new DisposableSubscriber<List<Monster>>() {
@Override
public void onNext(List<Monster> monsters) {
mAllMonsters.setValue(monsters);
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
}
private boolean monsterMatchesFilter(Monster monster, String filterText) {
if (StringHelper.isNullOrEmpty(filterText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.name, filterText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.size, filterText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.type, filterText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.subtype, filterText)) {
return true;
}
if (StringHelper.containsCaseInsensitive(monster.alignment, filterText)) {
return true;
}
return false;
}
private List<Monster> filterMonsters(List<Monster> allMonsters, String filterText) {
ArrayList<Monster> filteredMonsters = new ArrayList<>();
filterText = filterText.toLowerCase(Locale.ROOT);
if (allMonsters != null) {
for (Monster monster : allMonsters) {
// TODO: do the filtering like the iOS app does.
Logger.logUnimplementedFeature("do the filtering like the iOS app does");
// TODO: consider splitting search text into words and if each word appears in any of these fields return true e.g, "large demon" would match large in size and demon in type.
// TODO: add tags and search by tags
// TODO: add a display of what fields matched on each item in the results
// TODO: make the criteria configurable from this screen
// TODO: find a way to add challenge rating as a search criteria
if (monsterMatchesFilter(monster, filterText)) {
filteredMonsters.add(monster);
}
}
}
return filteredMonsters;
}
public LiveData<List<Monster>> getMatchedMonsters() {
return mFilteredMonsters;
}
public void setFilterText(String filterText) {
mFilterText.setValue(filterText);
}
}

View File

@@ -0,0 +1,17 @@
package com.majinnaibu.monstercards.ui.shared;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
public class SimpleListItemViewHolder<T> extends RecyclerView.ViewHolder {
public final TextView contentView;
public T item;
public SimpleListItemViewHolder(SimpleListItemBinding binding) {
super(binding.getRoot());
contentView = binding.content;
}
}

View File

@@ -25,7 +25,7 @@ public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
private final OnMoveCallback mOnMove; private final OnMoveCallback mOnMove;
private final Context mContext; private final Context mContext;
public SwipeToDeleteCallback(@NonNull Context context, OnSwipeCallback onDelete, OnMoveCallback onMove) { public SwipeToDeleteCallback(Context context, OnSwipeCallback onDelete, OnMoveCallback onMove) {
super(onMove == null ? 0 : ItemTouchHelper.UP | ItemTouchHelper.DOWN, onDelete == null ? 0 : ItemTouchHelper.LEFT); super(onMove == null ? 0 : ItemTouchHelper.UP | ItemTouchHelper.DOWN, onDelete == null ? 0 : ItemTouchHelper.LEFT);
mOnDelete = onDelete; mOnDelete = onDelete;
mOnMove = onMove; mOnMove = onMove;

View File

@@ -4,7 +4,6 @@ import androidx.lifecycle.MutableLiveData;
import java.util.Objects; import java.util.Objects;
@SuppressWarnings("unused")
public class ChangeTrackedLiveData<T> extends MutableLiveData<T> { public class ChangeTrackedLiveData<T> extends MutableLiveData<T> {
private final OnValueChangedCallback<T> mOnValueChangedCallback; private final OnValueChangedCallback<T> mOnValueChangedCallback;
private final OnValueDirtiedCallback mOnValueDirtiedCallback; private final OnValueDirtiedCallback mOnValueDirtiedCallback;

View File

@@ -0,0 +1,5 @@
package com.majinnaibu.monstercards.utils;
public interface ItemCallback<T> {
void onItem(T item);
}

View File

@@ -2,7 +2,6 @@ package com.majinnaibu.monstercards.utils;
import android.util.Log; import android.util.Log;
@SuppressWarnings("unused")
public class Logger { public class Logger {
public static final String LOG_TAG = "MonsterCards"; public static final String LOG_TAG = "MonsterCards";
@@ -38,7 +37,7 @@ public class Logger {
StackTraceElement stackTraceElement = throwable.getStackTrace()[0]; StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber(); String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String message = String.format("Unexpected error occurred at %s.", location); String message = String.format("Unexpected error occured at %s.", location);
Log.wtf(LOG_TAG, message, throwable); Log.wtf(LOG_TAG, message, throwable);
} }
@@ -56,7 +55,7 @@ public class Logger {
StackTraceElement stackTraceElement = throwable.getStackTrace()[0]; StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber(); String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String message = String.format("Unexpected error occurred at %s.", location); String message = String.format("Unexpected error occured at %s.", location);
Log.e(LOG_TAG, message, throwable); Log.e(LOG_TAG, message, throwable);
} }
@@ -74,7 +73,7 @@ public class Logger {
StackTraceElement stackTraceElement = throwable.getStackTrace()[0]; StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber(); String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String message = String.format("Unexpected error occurred at %s.", location); String message = String.format("Unexpected error occured at %s.", location);
Log.w(LOG_TAG, message, throwable); Log.w(LOG_TAG, message, throwable);
} }
@@ -92,7 +91,7 @@ public class Logger {
StackTraceElement stackTraceElement = throwable.getStackTrace()[0]; StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber(); String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String message = String.format("Unexpected error occurred at %s.", location); String message = String.format("Unexpected error occured at %s.", location);
Log.i(LOG_TAG, message, throwable); Log.i(LOG_TAG, message, throwable);
} }
@@ -110,7 +109,7 @@ public class Logger {
StackTraceElement stackTraceElement = throwable.getStackTrace()[0]; StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber(); String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String message = String.format("Unexpected error occurred at %s.", location); String message = String.format("Unexpected error occured at %s.", location);
Log.d(LOG_TAG, message, throwable); Log.d(LOG_TAG, message, throwable);
} }
@@ -128,7 +127,7 @@ public class Logger {
StackTraceElement stackTraceElement = throwable.getStackTrace()[0]; StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber(); String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
String message = String.format("Unexpected error occurred at %s.", location); String message = String.format("Unexpected error occured at %s.", location);
Log.v(LOG_TAG, message, throwable); Log.v(LOG_TAG, message, throwable);
} }

View File

@@ -3,7 +3,6 @@ package com.majinnaibu.monstercards.utils;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@SuppressWarnings("unused")
public class TextChangedListener implements TextWatcher { public class TextChangedListener implements TextWatcher {
private final BeforeTextChangedCallback mBeforeTextChangedCallback; private final BeforeTextChangedCallback mBeforeTextChangedCallback;

View File

@@ -17,7 +17,7 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/bottom_nav_menu" /> app:menu="@menu/bottom_nav_menu" />
<androidx.fragment.app.FragmentContainerView <fragment
android:id="@+id/nav_host_fragment" android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"
@@ -31,5 +31,4 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_bias="0.0"
app:navGraph="@navigation/mobile_navigation" /> app:navGraph="@navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -10,9 +10,9 @@
android:id="@+id/name" android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -23,9 +23,9 @@
android:id="@+id/advantage" android:id="@+id/advantage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@@ -35,9 +35,9 @@
android:id="@+id/proficiency" android:id="@+id/proficiency"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@@ -48,12 +48,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="+5" /> tools:text="+5" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -3,17 +3,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/action_card_height" android:layout_height="match_parent"
android:background="@drawable/rectangle_background"> android:background="@drawable/rectangle_background">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_normal" android:layout_marginHorizontal="8dp"
android:layout_marginTop="@dimen/padding_small" android:layout_marginTop="4dp"
android:textSize="@dimen/action_card_name_text_size" android:textSize="14sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -25,16 +25,14 @@
android:id="@+id/description" android:id="@+id/description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginHorizontal="@dimen/padding_normal" android:layout_marginHorizontal="8dp"
android:layout_marginBottom="@dimen/padding_normal" android:layout_marginBottom="8dp"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="false" android:singleLine="false"
android:textSize="@dimen/action_card_description_text_size" android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/name" app:layout_constraintTop_toBottomOf="@id/name"
tools:text="Melee Weapon Attack: +8 to hit, reach 10 ft., one target. Hit: 14 (2d8 + 5) bludgeoning damage." /> tools:text="Melee Weapon Attack: +8 to hit, reach 10 ft., one target. Hit: 14 (2d8 + 5) bludgeoning damage." />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,18 +2,18 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" android:layout_height="40dp"
android:background="@drawable/rectangle_background"> android:background="@drawable/rectangle_background">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:text="@string/label_armorClass_icon" android:text="AC"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -24,12 +24,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="17" /> tools:text="17" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,34 +2,33 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" android:layout_height="40dp"
android:background="@drawable/rectangle_background"> android:background="@drawable/rectangle_background">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:text="@string/label_challengeRating_icon"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
android:text="CR" />
<TextView <TextView
android:id="@+id/value" android:id="@+id/value"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="1/8" /> tools:text="1/8" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,18 +2,18 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" android:layout_height="40dp"
android:background="@drawable/rectangle_background"> android:background="@drawable/rectangle_background">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:text="@string/label_hitPoints_icon" android:text="HP"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -24,12 +24,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="367" /> tools:text="367" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,18 +2,18 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" android:layout_height="40dp"
android:background="@drawable/rectangle_background"> android:background="@drawable/rectangle_background">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_icon_border_padding" android:layout_margin="2dp"
android:text="@string/label_initiative_icon" android:text="INIT"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_tiny" android:textSize="8sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -24,12 +24,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@android:color/primary_text_light" android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_icon_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="+2" /> tools:text="+2" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -4,15 +4,15 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="@dimen/text_margin"
android:background="@drawable/rectangle_background"> android:background="@drawable/rectangle_background">
<TextView <TextView
android:id="@+id/name" android:id="@+id/name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
android:textSize="@dimen/monster_card_name_text_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -23,8 +23,8 @@
android:id="@+id/meta" android:id="@+id/meta"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
android:textSize="@dimen/monster_card_meta_text_size" android:textSize="12sp"
android:textStyle="italic" android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -35,8 +35,8 @@
android:id="@+id/challengeRating" android:id="@+id/challengeRating"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
android:textSize="@dimen/monster_card_name_text_size" android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="CR 1/8" /> tools:text="CR 1/8" />
@@ -45,7 +45,7 @@
android:id="@+id/boxesRow" android:id="@+id/boxesRow"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/meta"> app:layout_constraintTop_toBottomOf="@id/meta">
@@ -53,8 +53,8 @@
<include <include
android:id="@+id/strength" android:id="@+id/strength"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -64,8 +64,8 @@
<include <include
android:id="@+id/dexterity" android:id="@+id/dexterity"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -75,8 +75,8 @@
<include <include
android:id="@+id/constitution" android:id="@+id/constitution"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -86,8 +86,8 @@
<include <include
android:id="@+id/intelligence" android:id="@+id/intelligence"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -97,8 +97,8 @@
<include <include
android:id="@+id/wisdom" android:id="@+id/wisdom"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -108,8 +108,8 @@
<include <include
android:id="@+id/charisma" android:id="@+id/charisma"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -119,8 +119,8 @@
<include <include
android:id="@+id/armorClass" android:id="@+id/armorClass"
layout="@layout/card_armor_class" layout="@layout/card_armor_class"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -130,15 +130,15 @@
<include <include
android:id="@+id/hitPoints" android:id="@+id/hitPoints"
layout="@layout/card_hit_points" layout="@layout/card_hit_points"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="4dp"
android:layout_marginVertical="@dimen/padding_normal" android:layout_marginVertical="8dp"
android:baselineAligned="false" android:baselineAligned="false"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -149,26 +149,24 @@
android:id="@+id/action1" android:id="@+id/action1"
layout="@layout/card_action" layout="@layout/card_action"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="@dimen/action_card_height" android:layout_height="80dp"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="4dp"
android:layout_weight="1" /> android:layout_weight="1" />
<include <include
android:id="@+id/action2" android:id="@+id/action2"
layout="@layout/card_action" layout="@layout/card_action"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="@dimen/action_card_height" android:layout_height="80dp"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="4dp"
android:layout_weight="1" /> android:layout_weight="1" />
<include <include
android:id="@+id/action3" android:id="@+id/action3"
layout="@layout/card_action" layout="@layout/card_action"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="@dimen/action_card_height" android:layout_height="80dp"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="4dp"
android:layout_weight="1" /> android:layout_weight="1" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -10,8 +10,8 @@
android:id="@+id/name" android:id="@+id/name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
android:textSize="@dimen/monster_card_name_text_size" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -22,8 +22,8 @@
android:id="@+id/meta" android:id="@+id/meta"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
android:textSize="@dimen/monster_card_meta_text_size" android:textSize="12sp"
android:textStyle="italic" android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -34,8 +34,8 @@
android:id="@+id/challengeRating" android:id="@+id/challengeRating"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
android:textSize="@dimen/monster_card_name_text_size" android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="CR 1/8" /> tools:text="CR 1/8" />
@@ -44,7 +44,7 @@
android:id="@+id/boxesRow" android:id="@+id/boxesRow"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/padding_normal" android:layout_margin="8dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -53,8 +53,8 @@
<include <include
android:id="@+id/strength" android:id="@+id/strength"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -64,8 +64,8 @@
<include <include
android:id="@+id/dexterity" android:id="@+id/dexterity"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -75,8 +75,8 @@
<include <include
android:id="@+id/constitution" android:id="@+id/constitution"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -86,8 +86,8 @@
<include <include
android:id="@+id/intelligence" android:id="@+id/intelligence"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -97,8 +97,8 @@
<include <include
android:id="@+id/wisdom" android:id="@+id/wisdom"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -108,8 +108,8 @@
<include <include
android:id="@+id/charisma" android:id="@+id/charisma"
layout="@layout/card_ability_score" layout="@layout/card_ability_score"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -119,8 +119,8 @@
<include <include
android:id="@+id/armorClass" android:id="@+id/armorClass"
layout="@layout/card_armor_class" layout="@layout/card_armor_class"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
<Space <Space
android:layout_width="0dp" android:layout_width="0dp"
@@ -130,7 +130,7 @@
<include <include
android:id="@+id/hitPoints" android:id="@+id/hitPoints"
layout="@layout/card_hit_points" layout="@layout/card_hit_points"
android:layout_width="@dimen/icon_size" android:layout_width="40dp"
android:layout_height="@dimen/icon_size" /> android:layout_height="40dp" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -27,7 +27,7 @@
app:layout_constraintTop_toBottomOf="@id/label"> app:layout_constraintTop_toBottomOf="@id/label">
<com.google.android.material.radiobutton.MaterialRadioButton <com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/hasNoAdvantage" android:id="@+id/none"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="@dimen/padding_small"
@@ -43,7 +43,7 @@
tools:checked="true" /> tools:checked="true" />
<com.google.android.material.radiobutton.MaterialRadioButton <com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/hasAdvantage" android:id="@+id/advantage"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="@dimen/padding_small"
@@ -59,7 +59,7 @@
tools:checked="false" /> tools:checked="false" />
<com.google.android.material.radiobutton.MaterialRadioButton <com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/hasDisadvantage" android:id="@+id/disadvantage"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_small" android:layout_marginHorizontal="@dimen/padding_small"

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
tools:context=".ui.components.AdvantagePicker">
<!-- // TODO: style this control to look less awful by default -->
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/padding_normal"
android:layout_marginTop="@dimen/padding_small"
android:text="@string/label_advantage"
android:textAppearance="@android:style/TextAppearance.Material.Body1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RadioGroup
android:id="@+id/group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/label">
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/hasNoAdvantage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:background="@drawable/radio_button_selector"
android:button="@android:color/transparent"
android:gravity="center"
android:padding="8dp"
android:text="@string/label_advantage_none"
android:textAppearance="@android:style/TextAppearance.Material.Button"
android:textColor="@color/radio_button_text"
tools:checked="true" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/hasAdvantage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:background="@drawable/radio_button_selector"
android:button="@android:color/transparent"
android:gravity="center"
android:padding="8dp"
android:text="@string/label_advantage_advantage"
android:textAppearance="@android:style/TextAppearance.Material.Button"
android:textColor="@color/radio_button_text"
tools:checked="false" />
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/hasDisadvantage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="8dp"
android:layout_weight="1"
android:background="@drawable/radio_button_selector"
android:button="@android:color/transparent"
android:gravity="center"
android:padding="8dp"
android:text="@string/label_advantage_disadvantage"
android:textAppearance="@android:style/TextAppearance.Material.Button"
android:textColor="@color/radio_button_text"
tools:checked="false" />
</RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -10,10 +10,8 @@
android:id="@+id/list" android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"
android:divider="?android:attr/dividerVertical" android:divider="?android:attr/dividerVertical"
android:dividerPadding="@dimen/text_margin" android:dividerPadding="@dimen/text_margin"
android:padding="@dimen/padding_normal"
app:layoutManager="LinearLayoutManager" app:layoutManager="LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
<!-- <include-->
<!-- layout="@layout/card_monster"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_columnWeight="1"-->
<!-- android:layout_marginVertical="8dp"/>-->
<!-- <include-->
<!-- layout="@layout/card_monster_short"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_columnWeight="1"-->
<!-- android:layout_marginVertical="8dp"/>-->
<!-- <include-->
<!-- layout="@layout/tile_monster"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_columnWeight="1"-->
<!-- android:layout_marginVertical="8dp"/>-->
<!-- <include-->
<!-- layout="@layout/tile_monster_short"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_columnWeight="1"-->
<!-- android:layout_marginVertical="8dp" />-->
</LinearLayout>

View File

@@ -17,7 +17,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/fragment_edit_languages_list_item" /> tools:listitem="@layout/simple_list_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_language" android:id="@+id/add_language"

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<!-- understands but textbox --> <!-- understands but textbox -->

View File

@@ -181,9 +181,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin" android:layout_margin="@dimen/text_margin"
android:text="@string/label_regional_effects" android:text="@string/label_regional_actions"
android:textSize="@dimen/text_h4_size" android:textSize="@dimen/text_h4_size"
app:drawableEndCompat="@drawable/ic_chevron_right_24" /> app:drawableEndCompat="@drawable/ic_chevron_right_24" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -167,5 +167,4 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -17,7 +17,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/fragment_edit_skills_list_item" /> tools:listitem="@layout/simple_list_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_skill" android:id="@+id/add_skill"

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"
android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.editmonster.EditStringFragment">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="@dimen/text_margin"> android:layout_margin="@dimen/text_margin"
tools:context=".ui.editmonster.EditStringFragment">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/description" android:id="@+id/description"
@@ -18,5 +14,5 @@
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="text" android:inputType="text"
tools:text="blinded" /> tools:text="blinded" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</ScrollView>

View File

@@ -17,7 +17,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/fragment_edit_traits_list_item" /> tools:listitem="@layout/simple_list_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_item" android:id="@+id/add_item"

Some files were not shown because too many files have changed in this diff Show More