Compare commits
443 Commits
develop-io
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 70b7a4e795 | |||
| 8b4d9b6b17 | |||
| 6bad5a4ed5 | |||
| 93bf987bb9 | |||
| be433bf217 | |||
| 1ae81b03b0 | |||
| 4633a50bf4 | |||
| 9ac73337af | |||
| 25340cb7fd | |||
| be544f5304 | |||
| 3dc39ad6a4 | |||
| 92ea5b6a0d | |||
| a5eec5c4c8 | |||
| e598b2984a | |||
|
|
32ae8461b7 | ||
|
|
8686dbdaea | ||
|
|
a8df37325f | ||
|
|
2743a8337b | ||
|
|
b4767676d2 | ||
|
|
7c9037af84 | ||
|
|
c582ba5eaa | ||
|
|
8b2ab8f48f | ||
|
|
2de07c54cc | ||
|
|
c50f79c273 | ||
|
|
921a02f953 | ||
|
|
ec712842ca | ||
|
|
2f9dec4d02 | ||
|
|
cb1e3343c9 | ||
| 5bded410d2 | |||
| f6a032844c | |||
| d214d615e5 | |||
| 54f863ee5f | |||
| d4d298fcc3 | |||
| 7f31d98d7e | |||
| 68348b18c2 | |||
| 42a2994f2c | |||
| 218f39f6c2 | |||
| 2512bd8d75 | |||
| 370d416a23 | |||
| 60d139078b | |||
| 611fa6c323 | |||
| ab5a3c7c67 | |||
| b889857e80 | |||
| da0e072a45 | |||
| e075fc4369 | |||
| 0db46ebb51 | |||
| 20318b0ad5 | |||
| 6debb1eb27 | |||
| 87c845bd0d | |||
| f507f9d7cd | |||
| 8bb1f64e9f | |||
| f2d0e93911 | |||
| c9a7e028ae | |||
| 807871fe5c | |||
| acadf2170c | |||
| 8215d2021c | |||
| 0cbf6022c4 | |||
| eec695bfc8 | |||
| 8363912e53 | |||
| 67db8d79d0 | |||
| b5834f3db2 | |||
| ca6a319bd9 | |||
| 95ba20b5c6 | |||
| ea65692b38 | |||
| d7cf01e30d | |||
| 30c6dc7ee5 | |||
| f13be2c1ac | |||
| 920344b5fd | |||
| 7b3d6003d4 | |||
| c837c19b87 | |||
| b9759f6364 | |||
| f24f1d978c | |||
| 2defd1fca1 | |||
| 07f81a5f6d | |||
| 985c2fb730 | |||
| 0dc96a8c45 | |||
| 10bca503e5 | |||
| 1d335e9a37 | |||
| fbf119fb8a | |||
| 3d6adaad2c | |||
| c3a972571a | |||
| 4e45a547f4 | |||
| 792628d4a4 | |||
| b6b669a0db | |||
| 540a0474da | |||
| 49734d5eef | |||
| b2c3728e9a | |||
| 39cab7f799 | |||
|
|
1e007a3553 | ||
|
|
1a487f950d | ||
|
|
ea13e38402 | ||
|
|
8e2372085d | ||
|
|
129d910126 | ||
|
|
1a23e5e35a | ||
|
|
660cf633da | ||
|
|
1e7a7c68aa | ||
|
|
070fda0989 | ||
|
|
af05c41b75 | ||
|
|
efa4c2a299 | ||
|
|
2f5918b7a2 | ||
|
|
cb6f7122ed | ||
|
|
7cfc6d4f65 | ||
|
|
cdae6a8b39 | ||
|
|
9d46d1420e | ||
|
|
6d43b0635c | ||
|
|
4c138ee499 | ||
|
|
0fcfa7e782 | ||
|
|
791cf4164c | ||
|
|
fb12deaa3e | ||
|
|
6e597462ef | ||
|
|
12ffc5b15f | ||
|
|
9c81bd4905 | ||
|
|
f1cbc60857 | ||
|
|
a2798ddc82 | ||
|
|
ecf2b01723 | ||
|
|
18d6f2a31e | ||
|
|
7cbcf8d07c | ||
|
|
dc487d238a | ||
|
|
b8c702f665 | ||
|
|
259b59f519 | ||
|
|
5289bac908 | ||
|
|
34e11d97e5 | ||
|
|
ca6684a093 | ||
|
|
6e48a6f455 | ||
| b8af70406f | |||
| 7eae6f820e | |||
| 2076d53b11 | |||
| e20602cc3d | |||
| 04dc066191 | |||
| 72b3df429f | |||
| 8d94afeb55 | |||
| 8bae59ed29 | |||
| c401b7919e | |||
| 9983ba10cb | |||
| 171bc7436e | |||
| 04a30aa766 | |||
| 2356726e3f | |||
| 6151ad889c | |||
| 1f15d73573 | |||
| 40589f171d | |||
| a58c851240 | |||
| 2c6b514538 | |||
| f6a8b83343 | |||
| eb3fa5108f | |||
| 922db42322 | |||
| 71177b92f9 | |||
| 34e68443ae | |||
| 857733ec9c | |||
| 7e78ad8b7d | |||
| 430fa61be1 | |||
| 70e05cbb21 | |||
| b67073622f | |||
| c90579903d | |||
| 1a02eab07a | |||
| 00463c8092 | |||
| 153c49fe7b | |||
| 8178ec6fd7 | |||
| 2e7e40554d | |||
| 989440de83 | |||
| 3c4adacc17 | |||
| c28e1cb8c5 | |||
| 48dab535e9 | |||
| ff26cb64b7 | |||
| 28f0787020 | |||
| 71da064423 | |||
| 23bcdc237d | |||
| b56a662c9e | |||
| dcce64f91a | |||
| 595ee0c6fb | |||
| d52102d430 | |||
| 6a4abdd547 | |||
| bd3741af2d | |||
| e254adfdce | |||
| 9c973ef348 | |||
| dc9a0827d4 | |||
| b27274928e | |||
| 6bb1e419c8 | |||
| 3cda90eedd | |||
| 98a7dc5eeb | |||
| e384e29570 | |||
| 7aa6419ece | |||
| 6b953e320d | |||
| 9f56f0283a | |||
| b5f92afae9 | |||
| f58243ef6b | |||
| 886778ee78 | |||
| 0a85324734 | |||
| b374dbfe71 | |||
| 21af6e20ba | |||
| dc9066daca | |||
| e8e19d5371 | |||
| c5242b5206 | |||
| ac2e37e494 | |||
| c5d857435d | |||
| a1fab9d399 | |||
| e02e4ec399 | |||
| 8706240fb4 | |||
| e17c492baf | |||
| 2df11701e6 | |||
| c6c0e4f758 | |||
| 8c233a3bc7 | |||
| e98b72ad7d | |||
| 1fb8dc3a86 | |||
| 8b52b0c3e5 | |||
| ee065d7b39 | |||
| 59b319c27d | |||
| cdcb7a60d4 | |||
| 79106ec9f3 | |||
| bb4cbbb98b | |||
| 84b0fee261 | |||
| 0c3ab6dc39 | |||
| a694205c74 | |||
| 793987c3fb | |||
| 7d5f9c89a9 | |||
| 6da0bfe70a | |||
| a44893bca8 | |||
| 67375292a5 | |||
| 145c827417 | |||
| 8ff1cb8779 | |||
| 706b58fd2c | |||
| 15973a79f0 | |||
| 94edc44044 | |||
| 5396b7b014 | |||
| e0cc8560d1 | |||
| c627bb0873 | |||
| 6d8ec92012 | |||
| 212358e41d | |||
| 5fca394f0e | |||
| 6c914fb947 | |||
| 5a283b8dae | |||
| 407987e410 | |||
| 27a1dd7580 | |||
| 775fbf3d9b | |||
| 2a75de4bce | |||
| 72502b3d03 | |||
| 2b8a178c05 | |||
| ab306289bd | |||
| 74b0c6695f | |||
| 44d90ff5ea | |||
| 5113283550 | |||
|
|
2a963cea0a | ||
|
|
ab81917eb2 | ||
|
|
48f71d1a3a | ||
|
|
c4bb775af4 | ||
|
|
d1e3c3f5f3 | ||
| addda6f54f | |||
|
|
938f0fb758 | ||
| 43004a3a0a | |||
|
|
4396336cae | ||
|
|
833131c123 | ||
|
|
85e61dbb14 | ||
|
|
0fd2cb4e9a | ||
| 163cbe7bb3 | |||
| c7f4b4c137 | |||
| 739dea8db7 | |||
| ebf01e2bb0 | |||
| 346e3c1957 | |||
| d3a1878656 | |||
| 9ff561dd9d | |||
| 090e6d5263 | |||
| f31e89aacb | |||
| d212808ee1 | |||
| 0f187fdbe2 | |||
| 8006ee4b66 | |||
| 0d77e79b0f | |||
| ef05ac1c5a | |||
| e11bf1455d | |||
| 885b0e08f7 | |||
| c55b6e8f0d | |||
| 96a967f0d9 | |||
| 6a5c7b4384 | |||
| 1b0f2ee0df | |||
| f8b6c1c45c | |||
| 7022739c55 | |||
| 072feb5c52 | |||
| a7c068fd45 | |||
| f3544a581b | |||
| 027fdbd53e | |||
| 8002147b91 | |||
| 98b59ff445 | |||
| dd4fd92393 | |||
| aaba70761b | |||
| 85810a1ebb | |||
| 8676bc8e82 | |||
| fafdc952ac | |||
| 4bd749ce9c | |||
| 781e6cfb9e | |||
| 27893bff24 | |||
| 46e4b9a1f6 | |||
| f70390d585 | |||
| 9d7add9774 | |||
| c686582184 | |||
| 59d2b9769a | |||
| f62641bd21 | |||
| 6b30b8d12e | |||
| 71a90a8851 | |||
| 5aede274c8 | |||
| 429eb7aca8 | |||
| 89376e85a1 | |||
| 2e46676ecd | |||
| 6d95a5f094 | |||
| 4426ed52eb | |||
| ba7a680cfd | |||
| 4ea630ecca | |||
| 70e5f5edf0 | |||
| 5e0b998b70 | |||
| af1cd55bc9 | |||
| 5c1a524875 | |||
| 935ab63899 | |||
| baf28534e1 | |||
| 7ddf4932f8 | |||
| 7de324e922 | |||
| ab6dfea689 | |||
| fc2c908043 | |||
| 3599d8b2c1 | |||
| 96eddbbaa0 | |||
| a549e908be | |||
| 6459ad0125 | |||
| 23a33f7ffe | |||
| 0f6454ea41 | |||
| 5f4902305c | |||
| 423ee475f4 | |||
| 79ef9a50d1 | |||
| ae287d0551 | |||
| 69b207177c | |||
| 67c3ec480d | |||
| a59571aae8 | |||
| 3439dbda42 | |||
| eb7199d5a9 | |||
| bad8a374af | |||
| e2a54c7b89 | |||
| 6565d0a82d | |||
| 99298d3f28 | |||
| 3a7125ee88 | |||
| 5dee63fe54 | |||
| 5a9a1e87d3 | |||
| 920c3ca15e | |||
| c1bf39aba7 | |||
| c204a6baaf | |||
| c3feaf4f64 | |||
| 15e3cd3810 | |||
| cd813285d1 | |||
| 30b32592ed | |||
| 137d6f7c43 | |||
| a5344abeb0 | |||
| 3bb52bc368 | |||
| 518ff49907 | |||
| b5cb02937b | |||
| 8be1479357 | |||
| b79cb36335 | |||
| 65afc41197 | |||
| ec921aa705 | |||
| e057f6f12e | |||
| 9ced8e3407 | |||
| 757f75657f | |||
| fae10b17ed | |||
| 15f012a65a | |||
| 308cb755fe | |||
| b2c21e0542 | |||
| 828d65ad44 | |||
| b8eb935d9a | |||
| 533677baff | |||
| 7bbe86c901 | |||
| f23fe12e7b | |||
| 6341081fde | |||
| 5a2e98d35b | |||
| 16aa24150e | |||
| 44354f7c8f | |||
| f94c7863b7 | |||
| 93399a8bda | |||
| 1ecd4a4327 | |||
| 800e7ef6f1 | |||
| 84b0d246b5 | |||
| e21c755e62 | |||
| da6a03144a | |||
| b41b138f93 | |||
| c58f0909bb | |||
| c224c51f84 | |||
| 8f52940d98 | |||
| 555efac0c4 | |||
| 9bf1595f29 | |||
| 4a1145fd28 | |||
| 82625d4548 | |||
| bc23b55429 | |||
| 42baec2a38 | |||
| 8029fb7540 | |||
| 4ff6a28c67 | |||
| 3e93aa59b4 | |||
| bead4f8ee5 | |||
| 5aa88932f7 | |||
| a6f048cc2d | |||
| e541fdcae3 | |||
| a26031a04f | |||
| 05b3ad1280 | |||
| 26f92e43f6 | |||
| d3dd60fb2c | |||
| 91a99a5df3 | |||
| 2fd50a9f68 | |||
| 513e2c3511 | |||
| 0c1deecb5b | |||
| 42ddfbd52f | |||
| ee9994c2c8 | |||
| dd05b39ea9 | |||
| 68e2f84e21 | |||
| 8efb17a56c | |||
| fade50eef5 | |||
| 2e70466891 | |||
| fb6418cc80 | |||
| f0e804ce59 | |||
| 0e1a07972b | |||
| 25b9d06935 | |||
| 5f58e9e41d | |||
| 651a8e30d4 | |||
| 327a640b83 | |||
| 15a89785c6 | |||
| 437e18d2cc | |||
| e18f44a136 | |||
| 48792ceebe | |||
| 0ff71193d6 | |||
| d338d42912 | |||
| 3db7334ba4 | |||
| 757f0ded69 | |||
| d6f12b302a | |||
| bab5a55c3b | |||
| f688898d96 | |||
| 8a758448a0 | |||
| bfcef65da3 | |||
| 937aba27b1 | |||
| 90c28b6629 | |||
| 06c1e1b880 | |||
| f12465222f | |||
| 55ed75e36a | |||
| b8db6daa94 | |||
| 1d47e65b1e | |||
| 687c215e73 | |||
| 9a3bae2f5c | |||
| 3b4808a360 | |||
| 4263645966 | |||
| afdbe20f80 | |||
| e017b2859d | |||
| d3f1e8dae2 | |||
| 3af7380f37 | |||
| 58b30d59e9 |
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
._*
|
||||
thumbs.db
|
||||
17
Android/.gitignore
vendored
Normal file
17
Android/.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
/.idea/dictionaries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
/app/debug
|
||||
/app/release
|
||||
1
Android/.idea/.name
generated
Normal file
1
Android/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
MonsterCards
|
||||
116
Android/.idea/codeStyles/Project.xml
generated
Normal file
116
Android/.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,116 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
6
Android/.idea/compiler.xml
generated
Normal file
6
Android/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
</component>
|
||||
</project>
|
||||
20
Android/.idea/gradle.xml
generated
Normal file
20
Android/.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="1.8" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
7
Android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
7
Android/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="FieldCanBeLocal" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="TrivialIf" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
25
Android/.idea/jarRepositories.xml
generated
Normal file
25
Android/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
67
Android/.idea/misc.xml
generated
Normal file
67
Android/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<option name="myDefaultNullable" value="androidx.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="14">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
|
||||
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
|
||||
<item index="6" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="7" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
|
||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
|
||||
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
|
||||
<item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
|
||||
<item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
|
||||
<item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="14">
|
||||
<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="2" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
|
||||
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
|
||||
<item index="5" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
|
||||
<item index="6" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
|
||||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
|
||||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
|
||||
<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="12" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
|
||||
<item index="13" class="java.lang.String" itemvalue="lombok.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
12
Android/.idea/runConfigurations.xml
generated
Normal file
12
Android/.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<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.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
16
Android/.idea/saveactions_settings.xml
generated
Normal file
16
Android/.idea/saveactions_settings.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SaveActionSettings">
|
||||
<option name="actions">
|
||||
<set>
|
||||
<option value="activate" />
|
||||
<option value="activateOnShortcut" />
|
||||
<option value="activateOnBatch" />
|
||||
<option value="noActionIfCompileErrors" />
|
||||
<option value="organizeImports" />
|
||||
<option value="reformat" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="configurationPath" value="" />
|
||||
</component>
|
||||
</project>
|
||||
6
Android/.idea/vcs.xml
generated
Normal file
6
Android/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
4
Android/README.md
Normal file
4
Android/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
[](https://appcenter.ms)
|
||||
|
||||
# MonsterCards for Android
|
||||
|
||||
1
Android/app/.gitignore
vendored
Normal file
1
Android/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
109
Android/app/build.gradle
Normal file
109
Android/app/build.gradle
Normal file
@@ -0,0 +1,109 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'androidx.navigation.safeargs'
|
||||
}
|
||||
|
||||
Properties properties = new Properties()
|
||||
def propertiesFile = project.rootProject.file('local.properties')
|
||||
if (propertiesFile.exists()) {
|
||||
properties.load(propertiesFile.newDataInputStream())
|
||||
}
|
||||
def appCenterLocalSecret = properties.getProperty('appCenter.localSecret')
|
||||
def appCenterEnvSecret = System.getenv('APPCENTER_SECRET')
|
||||
def appCenterSecret = appCenterLocalSecret != null ? appCenterLocalSecret : appCenterEnvSecret != null ? appCenterEnvSecret : ""
|
||||
def appCenterSdkVersion = '3.3.0'
|
||||
def nav_version = '2.3.5'
|
||||
def room_version = '2.3.0'
|
||||
def rxjava_version = '3.0.0'
|
||||
def flipper_version = '0.87.0'
|
||||
def soloader_version = '0.10.1'
|
||||
def gson_version = '2.8.6'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.majinnaibu.monstercards"
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
buildConfigField "String", "APPCENTER_SECRET", "\"${appCenterSecret}\""
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// Enables code shrinking, obfuscation, and optimization for only
|
||||
// your project's release build type.
|
||||
minifyEnabled true
|
||||
|
||||
// Enables resource shrinking, which is performed by the
|
||||
// Android Gradle plugin.
|
||||
shrinkResources true
|
||||
|
||||
// Includes the default ProGuard rules files that are packaged with
|
||||
// the Android Gradle plugin. To learn more, go to the section about
|
||||
// R8 configuration files.
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Included libs
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
||||
// Google
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
|
||||
|
||||
// Testing
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
||||
// Room DB
|
||||
implementation "io.reactivex.rxjava3:rxjava:$rxjava_version"
|
||||
implementation "io.reactivex.rxjava3:rxandroid:$rxjava_version"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-rxjava3:$room_version"
|
||||
//testImplementation "androidx.room:room-testing:$room_version"
|
||||
|
||||
// AppCenter
|
||||
debugImplementation "com.microsoft.appcenter:appcenter-analytics:${appCenterSdkVersion}"
|
||||
debugImplementation "com.microsoft.appcenter:appcenter-crashes:${appCenterSdkVersion}"
|
||||
|
||||
// Flipper
|
||||
debugImplementation "com.facebook.flipper:flipper:$flipper_version"
|
||||
debugImplementation "com.facebook.soloader:soloader:$soloader_version"
|
||||
releaseImplementation "com.facebook.flipper:flipper-noop:$flipper_version"
|
||||
|
||||
// Other 3rd Party
|
||||
implementation 'com.atlassian.commonmark:commonmark:0.15.2'
|
||||
implementation "com.google.code.gson:gson:$gson_version"
|
||||
}
|
||||
32
Android/app/proguard-rules.pro
vendored
Normal file
32
Android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep enum com.majinnaibu.monstercards.data.enums.AbilityScore
|
||||
-keep enum com.majinnaibu.monstercards.data.enums.ProficiencyType
|
||||
-keep enum com.majinnaibu.monstercards.data.enums.AdvantageType
|
||||
-keep enum com.majinnaibu.monstercards.data.enums.TraitType
|
||||
-keep enum com.majinnaibu.monstercards.data.enums.StringType
|
||||
-keepclassmembers,allowoptimization enum * {
|
||||
<fields>;
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "db1293d2f490940b55ca1f4f56b21b1a",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "Monster",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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, `blindsight_range` INTEGER NOT NULL DEFAULT 0, `is_blind_beyond_blindsight_range` INTEGER NOT NULL DEFAULT false, `darkvision_range` INTEGER NOT NULL DEFAULT 0, `tremorsense_range` INTEGER NOT NULL DEFAULT 0, `truesight_range` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `skills` TEXT, `damage_immunities` TEXT, `damage_resistances` TEXT, `damage_vulnerabilities` TEXT, `condition_immunities` TEXT, `languages` TEXT, `abilities` TEXT, `actions` TEXT, `reactions` TEXT, `lair_actions` TEXT, `legendary_actions` TEXT, `regional_actions` TEXT, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtype",
|
||||
"columnName": "subtype",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "alignment",
|
||||
"columnName": "alignment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthScore",
|
||||
"columnName": "strength_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthSavingThrowAdvantage",
|
||||
"columnName": "strength_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthSavingThrowProficiency",
|
||||
"columnName": "strength_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexterityScore",
|
||||
"columnName": "dexterity_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||
"columnName": "dexterity_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexteritySavingThrowProficiency",
|
||||
"columnName": "dexterity_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionScore",
|
||||
"columnName": "constitution_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||
"columnName": "constitution_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionSavingThrowProficiency",
|
||||
"columnName": "constitution_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceScore",
|
||||
"columnName": "intelligence_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||
"columnName": "intelligence_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||
"columnName": "intelligence_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomScore",
|
||||
"columnName": "wisdom_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||
"columnName": "wisdom_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomSavingThrowProficiency",
|
||||
"columnName": "wisdom_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaScore",
|
||||
"columnName": "charisma_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaSavingThrowAdvantage",
|
||||
"columnName": "charisma_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaSavingThrowProficiency",
|
||||
"columnName": "charisma_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "armorType",
|
||||
"columnName": "armor_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "shieldBonus",
|
||||
"columnName": "shield_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "naturalArmorBonus",
|
||||
"columnName": "natural_armor_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "otherArmorDescription",
|
||||
"columnName": "other_armor_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hitDice",
|
||||
"columnName": "hit_dice",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomHP",
|
||||
"columnName": "has_custom_hit_points",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "customHPDescription",
|
||||
"columnName": "custom_hit_points_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "walkSpeed",
|
||||
"columnName": "walk_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "burrowSpeed",
|
||||
"columnName": "burrow_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "climbSpeed",
|
||||
"columnName": "climb_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "flySpeed",
|
||||
"columnName": "fly_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "canHover",
|
||||
"columnName": "can_hover",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "swimSpeed",
|
||||
"columnName": "swim_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomSpeed",
|
||||
"columnName": "has_custom_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customSpeedDescription",
|
||||
"columnName": "custom_speed_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "challengeRating",
|
||||
"columnName": "challenge_rating",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'1'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customChallengeRatingDescription",
|
||||
"columnName": "custom_challenge_rating_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customProficiencyBonus",
|
||||
"columnName": "custom_proficiency_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "blindsightRange",
|
||||
"columnName": "blindsight_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBlindBeyondBlindsightRange",
|
||||
"columnName": "is_blind_beyond_blindsight_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "darkvisionRange",
|
||||
"columnName": "darkvision_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tremorsenseRange",
|
||||
"columnName": "tremorsense_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "truesightRange",
|
||||
"columnName": "truesight_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "telepathyRange",
|
||||
"columnName": "telepathy_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "understandsButDescription",
|
||||
"columnName": "understands_but_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "skills",
|
||||
"columnName": "skills",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageImmunities",
|
||||
"columnName": "damage_immunities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageResistances",
|
||||
"columnName": "damage_resistances",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageVulnerabilities",
|
||||
"columnName": "damage_vulnerabilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "conditionImmunities",
|
||||
"columnName": "condition_immunities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "languages",
|
||||
"columnName": "languages",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "abilities",
|
||||
"columnName": "abilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actions",
|
||||
"columnName": "actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lairActions",
|
||||
"columnName": "lair_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "legendaryActions",
|
||||
"columnName": "legendary_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "regionalActions",
|
||||
"columnName": "regional_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'db1293d2f490940b55ca1f4f56b21b1a')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "6f1e7a2b2ab96fc4be4da1657a7a0138",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "monsters",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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, `blindsight_range` INTEGER NOT NULL DEFAULT 0, `is_blind_beyond_blindsight_range` INTEGER NOT NULL DEFAULT false, `darkvision_range` INTEGER NOT NULL DEFAULT 0, `tremorsense_range` INTEGER NOT NULL DEFAULT 0, `truesight_range` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `skills` TEXT, `damage_immunities` TEXT, `damage_resistances` TEXT, `damage_vulnerabilities` TEXT, `condition_immunities` TEXT, `languages` TEXT, `abilities` TEXT, `actions` TEXT, `reactions` TEXT, `lair_actions` TEXT, `legendary_actions` TEXT, `regional_actions` TEXT, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtype",
|
||||
"columnName": "subtype",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "alignment",
|
||||
"columnName": "alignment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthScore",
|
||||
"columnName": "strength_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthSavingThrowAdvantage",
|
||||
"columnName": "strength_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthSavingThrowProficiency",
|
||||
"columnName": "strength_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexterityScore",
|
||||
"columnName": "dexterity_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||
"columnName": "dexterity_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexteritySavingThrowProficiency",
|
||||
"columnName": "dexterity_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionScore",
|
||||
"columnName": "constitution_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||
"columnName": "constitution_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionSavingThrowProficiency",
|
||||
"columnName": "constitution_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceScore",
|
||||
"columnName": "intelligence_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||
"columnName": "intelligence_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||
"columnName": "intelligence_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomScore",
|
||||
"columnName": "wisdom_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||
"columnName": "wisdom_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomSavingThrowProficiency",
|
||||
"columnName": "wisdom_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaScore",
|
||||
"columnName": "charisma_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaSavingThrowAdvantage",
|
||||
"columnName": "charisma_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaSavingThrowProficiency",
|
||||
"columnName": "charisma_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "armorType",
|
||||
"columnName": "armor_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "shieldBonus",
|
||||
"columnName": "shield_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "naturalArmorBonus",
|
||||
"columnName": "natural_armor_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "otherArmorDescription",
|
||||
"columnName": "other_armor_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hitDice",
|
||||
"columnName": "hit_dice",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomHP",
|
||||
"columnName": "has_custom_hit_points",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "customHPDescription",
|
||||
"columnName": "custom_hit_points_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "walkSpeed",
|
||||
"columnName": "walk_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "burrowSpeed",
|
||||
"columnName": "burrow_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "climbSpeed",
|
||||
"columnName": "climb_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "flySpeed",
|
||||
"columnName": "fly_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "canHover",
|
||||
"columnName": "can_hover",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "swimSpeed",
|
||||
"columnName": "swim_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomSpeed",
|
||||
"columnName": "has_custom_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customSpeedDescription",
|
||||
"columnName": "custom_speed_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "challengeRating",
|
||||
"columnName": "challenge_rating",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'1'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customChallengeRatingDescription",
|
||||
"columnName": "custom_challenge_rating_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customProficiencyBonus",
|
||||
"columnName": "custom_proficiency_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "blindsightRange",
|
||||
"columnName": "blindsight_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isBlindBeyondBlindsightRange",
|
||||
"columnName": "is_blind_beyond_blindsight_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "darkvisionRange",
|
||||
"columnName": "darkvision_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "tremorsenseRange",
|
||||
"columnName": "tremorsense_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "truesightRange",
|
||||
"columnName": "truesight_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "telepathyRange",
|
||||
"columnName": "telepathy_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "understandsButDescription",
|
||||
"columnName": "understands_but_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "skills",
|
||||
"columnName": "skills",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageImmunities",
|
||||
"columnName": "damage_immunities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageResistances",
|
||||
"columnName": "damage_resistances",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageVulnerabilities",
|
||||
"columnName": "damage_vulnerabilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "conditionImmunities",
|
||||
"columnName": "condition_immunities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "languages",
|
||||
"columnName": "languages",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "abilities",
|
||||
"columnName": "abilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actions",
|
||||
"columnName": "actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lairActions",
|
||||
"columnName": "lair_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "legendaryActions",
|
||||
"columnName": "legendary_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "regionalActions",
|
||||
"columnName": "regional_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"ftsVersion": "FTS4",
|
||||
"ftsOptions": {
|
||||
"tokenizer": "simple",
|
||||
"tokenizerArgs": [],
|
||||
"contentTable": "monsters",
|
||||
"languageIdColumnName": "",
|
||||
"matchInfo": "FTS4",
|
||||
"notIndexedColumns": [],
|
||||
"prefixSizes": [],
|
||||
"preferredOrder": "ASC"
|
||||
},
|
||||
"contentSyncTriggers": [
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||
],
|
||||
"tableName": "monsters_fts",
|
||||
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtype",
|
||||
"columnName": "subtype",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "alignment",
|
||||
"columnName": "alignment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6f1e7a2b2ab96fc4be4da1657a7a0138')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "7c3c3ed79c7002102e7af7cfd21c23e0",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "monsters",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtype",
|
||||
"columnName": "subtype",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "alignment",
|
||||
"columnName": "alignment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthScore",
|
||||
"columnName": "strength_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthSavingThrowAdvantage",
|
||||
"columnName": "strength_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "strengthSavingThrowProficiency",
|
||||
"columnName": "strength_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexterityScore",
|
||||
"columnName": "dexterity_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexteritySavingThrowAdvantage",
|
||||
"columnName": "dexterity_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "dexteritySavingThrowProficiency",
|
||||
"columnName": "dexterity_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionScore",
|
||||
"columnName": "constitution_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionSavingThrowAdvantage",
|
||||
"columnName": "constitution_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "constitutionSavingThrowProficiency",
|
||||
"columnName": "constitution_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceScore",
|
||||
"columnName": "intelligence_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceSavingThrowAdvantage",
|
||||
"columnName": "intelligence_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "intelligenceSavingThrowProficiency",
|
||||
"columnName": "intelligence_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomScore",
|
||||
"columnName": "wisdom_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomSavingThrowAdvantage",
|
||||
"columnName": "wisdom_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "wisdomSavingThrowProficiency",
|
||||
"columnName": "wisdom_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaScore",
|
||||
"columnName": "charisma_score",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "10"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaSavingThrowAdvantage",
|
||||
"columnName": "charisma_saving_throw_advantage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "charismaSavingThrowProficiency",
|
||||
"columnName": "charisma_saving_throw_proficiency",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "armorType",
|
||||
"columnName": "armor_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'none'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "shieldBonus",
|
||||
"columnName": "shield_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "naturalArmorBonus",
|
||||
"columnName": "natural_armor_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "otherArmorDescription",
|
||||
"columnName": "other_armor_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hitDice",
|
||||
"columnName": "hit_dice",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "1"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomHP",
|
||||
"columnName": "has_custom_hit_points",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "customHPDescription",
|
||||
"columnName": "custom_hit_points_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "walkSpeed",
|
||||
"columnName": "walk_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "burrowSpeed",
|
||||
"columnName": "burrow_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "climbSpeed",
|
||||
"columnName": "climb_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "flySpeed",
|
||||
"columnName": "fly_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "canHover",
|
||||
"columnName": "can_hover",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "swimSpeed",
|
||||
"columnName": "swim_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomSpeed",
|
||||
"columnName": "has_custom_speed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customSpeedDescription",
|
||||
"columnName": "custom_speed_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "challengeRating",
|
||||
"columnName": "challenge_rating",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'1'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customChallengeRatingDescription",
|
||||
"columnName": "custom_challenge_rating_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "customProficiencyBonus",
|
||||
"columnName": "custom_proficiency_bonus",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "telepathyRange",
|
||||
"columnName": "telepathy_range",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "understandsButDescription",
|
||||
"columnName": "understands_but_description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "senses",
|
||||
"columnName": "senses",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "skills",
|
||||
"columnName": "skills",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageImmunities",
|
||||
"columnName": "damage_immunities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageResistances",
|
||||
"columnName": "damage_resistances",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "damageVulnerabilities",
|
||||
"columnName": "damage_vulnerabilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "conditionImmunities",
|
||||
"columnName": "condition_immunities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "languages",
|
||||
"columnName": "languages",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "abilities",
|
||||
"columnName": "abilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "actions",
|
||||
"columnName": "actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lairActions",
|
||||
"columnName": "lair_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "legendaryActions",
|
||||
"columnName": "legendary_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
},
|
||||
{
|
||||
"fieldPath": "regionalActions",
|
||||
"columnName": "regional_actions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "'[]'"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"ftsVersion": "FTS4",
|
||||
"ftsOptions": {
|
||||
"tokenizer": "simple",
|
||||
"tokenizerArgs": [],
|
||||
"contentTable": "monsters",
|
||||
"languageIdColumnName": "",
|
||||
"matchInfo": "FTS4",
|
||||
"notIndexedColumns": [],
|
||||
"prefixSizes": [],
|
||||
"preferredOrder": "ASC"
|
||||
},
|
||||
"contentSyncTriggers": [
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_UPDATE BEFORE UPDATE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_BEFORE_DELETE BEFORE DELETE ON `monsters` BEGIN DELETE FROM `monsters_fts` WHERE `docid`=OLD.`rowid`; END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_UPDATE AFTER UPDATE ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END",
|
||||
"CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_monsters_fts_AFTER_INSERT AFTER INSERT ON `monsters` BEGIN INSERT INTO `monsters_fts`(`docid`, `name`, `size`, `type`, `subtype`, `alignment`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`size`, NEW.`type`, NEW.`subtype`, NEW.`alignment`); END"
|
||||
],
|
||||
"tableName": "monsters_fts",
|
||||
"createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "size",
|
||||
"columnName": "size",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "subtype",
|
||||
"columnName": "subtype",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "alignment",
|
||||
"columnName": "alignment",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7c3c3ed79c7002102e7af7cfd21c23e0')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
## Monster
|
||||
id: UUID as TEXT // doesn't exist in the iOS model
|
||||
abilities: Set<Trait> converted to JSON as TEXT
|
||||
actions: Set<Trait> converted to JSON as TEXT
|
||||
alignment: String as TEXT
|
||||
armor_type: Enum<String> as TEXT
|
||||
blindsight_range: int as INTEGER
|
||||
burrow_speed: int as INTEGER
|
||||
can_hover: boolean as INTEGER
|
||||
challenge_rating: Enum<String> as TEXT
|
||||
charisma_saving_throw_advantage
|
||||
charisma_saving_throw_proficiency
|
||||
charisma_score: int as INTEGER
|
||||
climb_speed: int as INTEGER
|
||||
condition_immunities: Set<String> converted to JSON as TEXT
|
||||
constitution_saving_throw_advantage
|
||||
constitution_saving_throw_proficiency
|
||||
constitution_score: int as INTEGER
|
||||
//other_armor_description: String as TEXT
|
||||
custom_challenge_rating_description: String as TEXT
|
||||
custom_hit_points_description: String
|
||||
custom_proficiency_bonus: int as INTEGER
|
||||
custom_speed_description: String as TEXT
|
||||
damage_immunities: Set<String> converted to JSON as TEXT
|
||||
damage_resistances: Set<String> converted to JSON as TEXT
|
||||
damage_vulnerabilities: Set<String> converted to JSON as TEXT
|
||||
darkvision_range: int as INTEGER
|
||||
dexterity_saving_throw_advantage
|
||||
dexterity_saving_throw_proficiency
|
||||
dexterity_score: int as INTEGER
|
||||
fly_speed: int as INTEGER
|
||||
has_custom_hit_points: boolean as INTEGER
|
||||
has_custom_speed: boolean as INTEGER
|
||||
// has_shield
|
||||
hit_dice: int as INTEGER
|
||||
intelligence_saving_throw_advantage
|
||||
intelligence_saving_throw_proficiency
|
||||
intelligence_score: int as INTEGER
|
||||
is_blind_beyond_blindsight_range: boolean as INTEGER
|
||||
lair_actions
|
||||
languages: Set<Language> converted to JSON as TEXT
|
||||
legendary_actions
|
||||
name: String as TEXT
|
||||
natural_armor_bonus: int as INTEGER
|
||||
other_armor_description: String as TEXT
|
||||
reactions
|
||||
regional_actions
|
||||
// senses
|
||||
shield_bonus: int as INTEGER
|
||||
size: String as TEXT
|
||||
strength_saving_throw_advantage
|
||||
strength_saving_throw_proficiency
|
||||
strength_score: int as INTEGER
|
||||
tag: String as TEXT // subtype || tag
|
||||
swim_speed: int as INTEGER
|
||||
telepathy_range: int as INTEGER
|
||||
tremorsense_range: int as INTEGER
|
||||
truesight_range: int as INTEGER
|
||||
type: String as TEXT
|
||||
understands_but_description: String as TEXT
|
||||
walk_speed: int as INTEGER
|
||||
wisdom_saving_throw_advantage
|
||||
wisdom_saving_throw_proficiency
|
||||
wisdom_score: int as INTEGER
|
||||
|
||||
// tracked as relationship (don't do this)
|
||||
skills: Set<Skill> converted to JSON as TEXT
|
||||
|
||||
## Skill
|
||||
// ability_score_name String defaults to "strength"
|
||||
// advantage String defaults to "none"
|
||||
// name String defaults to ""
|
||||
// proficiency String defaults to "none"
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.majinnaibu.monstercards", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.majinnaibu.monstercards.init;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.majinnaibu.monstercards.BuildConfig;
|
||||
import com.microsoft.appcenter.AppCenter;
|
||||
import com.microsoft.appcenter.analytics.Analytics;
|
||||
import com.microsoft.appcenter.crashes.Crashes;
|
||||
|
||||
public class AppCenterInitializer {
|
||||
|
||||
public static void init(Application app) {
|
||||
if (BuildConfig.APPCENTER_SECRET != null && !"".equals(BuildConfig.APPCENTER_SECRET)) {
|
||||
AppCenter.start(
|
||||
app,
|
||||
BuildConfig.APPCENTER_SECRET,
|
||||
Analytics.class,
|
||||
Crashes.class
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.majinnaibu.monstercards.init;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDestination;
|
||||
|
||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||
import com.facebook.flipper.android.utils.FlipperUtils;
|
||||
import com.facebook.flipper.core.FlipperClient;
|
||||
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import com.google.gson.Gson;
|
||||
import com.majinnaibu.monstercards.BuildConfig;
|
||||
|
||||
public class FlipperInitializer {
|
||||
|
||||
public static void init(Context ctx) {
|
||||
SoLoader.init(ctx, false);
|
||||
|
||||
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(ctx)) {
|
||||
final FlipperClient client = AndroidFlipperClient.getInstance(ctx);
|
||||
client.addPlugin(new InspectorFlipperPlugin(ctx, DescriptorMapping.withDefaults()));
|
||||
client.addPlugin(new DatabasesFlipperPlugin(ctx));
|
||||
client.addPlugin(new SharedPreferencesFlipperPlugin(ctx));
|
||||
client.addPlugin(NavigationFlipperPlugin.getInstance());
|
||||
client.start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendNavigationEvent(NavController controller, NavDestination destination, Bundle arguments) {
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(arguments != null ? arguments : new Bundle());
|
||||
NavigationFlipperPlugin.getInstance().sendNavigationEvent(String.format("%s:%s", destination.getLabel(), json), null, null);
|
||||
}
|
||||
}
|
||||
56
Android/app/src/main/AndroidManifest.xml
Normal file
56
Android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.majinnaibu.monstercards">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".MonsterCardsApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<action android:name="android.intent.action.PICK" />
|
||||
<action android:name="android.intent.action.INSERT" />
|
||||
<action android:name="android.intent.action.INSERT_OR_EDIT" />
|
||||
|
||||
<category android:name="android.intent.category.ALTERNATIVE" />
|
||||
<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data
|
||||
android:mimeType="text/plain"
|
||||
android:scheme="content" />
|
||||
<data
|
||||
android:mimeType="application/octet-stream"
|
||||
android:scheme="content" />
|
||||
<data
|
||||
android:mimeType="text/plain"
|
||||
android:scheme="file" />
|
||||
</intent-filter>
|
||||
|
||||
<nav-graph android:value="@navigation/mobile_navigation" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
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.converters.ArmorTypeConverter;
|
||||
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
||||
import com.majinnaibu.monstercards.data.converters.ListOfTraitsConverter;
|
||||
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.SetOfStringConverter;
|
||||
import com.majinnaibu.monstercards.data.converters.UUIDConverter;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.MonsterFTS;
|
||||
|
||||
@Database(entities = {Monster.class, MonsterFTS.class}, version = 3)
|
||||
@TypeConverters({
|
||||
ArmorTypeConverter.class,
|
||||
ChallengeRatingConverter.class,
|
||||
ListOfTraitsConverter.class,
|
||||
SetOfLanguageConverter.class,
|
||||
SetOfSavingThrowConverter.class,
|
||||
SetOfSkillConverter.class,
|
||||
SetOfStringConverter.class,
|
||||
UUIDConverter.class,
|
||||
})
|
||||
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();
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.init.AppCenterInitializer;
|
||||
import com.majinnaibu.monstercards.init.FlipperInitializer;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
getOnBackPressedDispatcher().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
AppCenterInitializer.init(getApplication());
|
||||
setContentView(R.layout.activity_main);
|
||||
BottomNavigationView navView = findViewById(R.id.nav_view);
|
||||
// Passing each menu ID as a set of Ids because each
|
||||
// menu should be considered as top level destinations.
|
||||
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
|
||||
R.id.navigation_search,
|
||||
R.id.navigation_dashboard,
|
||||
R.id.navigation_collections,
|
||||
R.id.navigation_library)
|
||||
.build();
|
||||
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
|
||||
NavController navController = navHostFragment.getNavController();
|
||||
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
||||
FlipperInitializer.sendNavigationEvent(controller, destination, arguments);
|
||||
});
|
||||
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
|
||||
NavigationUI.setupWithNavController(navView, navController);
|
||||
onNewIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
String json = readMonsterJSONFromIntent(intent);
|
||||
if (!StringHelper.isNullOrEmpty(json)) {
|
||||
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
|
||||
NavController navController = navHostFragment.getNavController();
|
||||
NavDirections action = MobileNavigationDirections.actionGlobalMonsterImportFragment(json);
|
||||
navController.navigate(action);
|
||||
}
|
||||
}
|
||||
|
||||
private String readMonsterJSONFromIntent(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
Bundle extras = intent.getExtras();
|
||||
String type = intent.getType();
|
||||
String json;
|
||||
Uri uri = null;
|
||||
if ("android.intent.action.SEND".equals(action) && "text/plain".equals(type)) {
|
||||
uri = extras.getParcelable("android.intent.extra.STREAM");
|
||||
} else if ("android.intent.action.VIEW".equals(action) && ("text/plain".equals(type) || "application/octet-stream".equals(type))) {
|
||||
uri = intent.getData();
|
||||
} else {
|
||||
Logger.logError(String.format("unexpected launch configuration action: %s, type: %s, uri: %s", action, type, uri));
|
||||
}
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
json = readContentsOfUri(uri);
|
||||
if (StringHelper.isNullOrEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
private String readContentsOfUri(Uri uri) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
try (InputStream inputStream =
|
||||
getContentResolver().openInputStream(uri);
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(Objects.requireNonNull(inputStream)))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
builder.append(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.logError("error reading file", e);
|
||||
return null;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.init.FlipperInitializer;
|
||||
|
||||
public class MonsterCardsApplication extends Application {
|
||||
|
||||
|
||||
private MonsterRepository m_monsterLibraryRepository;
|
||||
|
||||
|
||||
public MonsterCardsApplication() {
|
||||
}
|
||||
|
||||
public MonsterRepository getMonsterRepository() {
|
||||
return m_monsterLibraryRepository;
|
||||
}
|
||||
|
||||
// Called when the application is starting, before any other application objects have been created.
|
||||
// Overriding this method is totally optional!
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
// Required initialization logic here!
|
||||
|
||||
FlipperInitializer.init(this);
|
||||
AppDatabase mDB = AppDatabase.getInstance(this);
|
||||
m_monsterLibraryRepository = new MonsterRepository(mDB);
|
||||
}
|
||||
|
||||
// Called by the system when the device configuration changes while your component is running.
|
||||
// Overriding this method is totally optional!
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
// This is called when the overall system is running low on memory,
|
||||
// and would like actively running processes to tighten their belts.
|
||||
// Overriding this method is totally optional!
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.majinnaibu.monstercards.data;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class DevContent {
|
||||
public static Monster createSampleMonster() {
|
||||
Monster monster = new Monster();
|
||||
// Name
|
||||
monster.name = "Pixie";
|
||||
// Meta
|
||||
monster.size = "tiny";
|
||||
monster.type = "fey";
|
||||
monster.subtype = "";
|
||||
monster.alignment = "neutral good";
|
||||
monster.armorType = ArmorType.NONE;
|
||||
// Armor & Armor Class
|
||||
monster.shieldBonus = 0;
|
||||
monster.naturalArmorBonus = 7;
|
||||
monster.otherArmorDescription = "14";
|
||||
// Hit Points
|
||||
monster.hitDice = 1;
|
||||
monster.hasCustomHP = false;
|
||||
monster.customHPDescription = "11 (2d8 + 2)";
|
||||
monster.walkSpeed = 10;
|
||||
monster.burrowSpeed = 0;
|
||||
monster.climbSpeed = 0;
|
||||
monster.flySpeed = 30;
|
||||
monster.canHover = false;
|
||||
monster.swimSpeed = 0;
|
||||
monster.hasCustomSpeed = false;
|
||||
monster.customSpeedDescription = "30 ft., swim 30 ft.";
|
||||
// Ability Scores
|
||||
monster.strengthScore = Integer.parseInt("2");
|
||||
monster.dexterityScore = Integer.parseInt("20");
|
||||
monster.constitutionScore = Integer.parseInt("8");
|
||||
monster.intelligenceScore = Integer.parseInt("10");
|
||||
monster.wisdomScore = Integer.parseInt("14");
|
||||
monster.charismaScore = Integer.parseInt("15");
|
||||
// monster.strengthScore = 10;
|
||||
// monster.dexterityScore = 10;
|
||||
// monster.constitutionScore = 10;
|
||||
// monster.intelligenceScore = 10;
|
||||
// monster.wisdomScore = 10;
|
||||
// monster.charismaScore = 10;
|
||||
|
||||
// Saving Throws
|
||||
monster.strengthSavingThrowAdvantage = AdvantageType.NONE;
|
||||
monster.strengthSavingThrowProficiency = ProficiencyType.NONE;
|
||||
monster.dexteritySavingThrowAdvantage = AdvantageType.ADVANTAGE;
|
||||
monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
monster.constitutionSavingThrowAdvantage = AdvantageType.DISADVANTAGE;
|
||||
monster.constitutionSavingThrowProficiency = ProficiencyType.EXPERTISE;
|
||||
monster.intelligenceSavingThrowAdvantage = AdvantageType.NONE;
|
||||
monster.intelligenceSavingThrowProficiency = ProficiencyType.EXPERTISE;
|
||||
monster.wisdomSavingThrowAdvantage = AdvantageType.ADVANTAGE;
|
||||
monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
monster.charismaSavingThrowAdvantage = AdvantageType.DISADVANTAGE;
|
||||
monster.charismaSavingThrowProficiency = ProficiencyType.NONE;
|
||||
//Skills
|
||||
monster.skills.add(new Skill("perception", AbilityScore.WISDOM));
|
||||
monster.skills.add(new Skill("stealth", AbilityScore.DEXTERITY));
|
||||
// Damage Types
|
||||
monster.damageImmunities.add("force");
|
||||
monster.damageImmunities.add("lightning");
|
||||
monster.damageImmunities.add("poison");
|
||||
monster.damageResistances.add("cold");
|
||||
monster.damageResistances.add("fire");
|
||||
monster.damageResistances.add("piercing");
|
||||
monster.damageVulnerabilities.add("acid");
|
||||
monster.damageVulnerabilities.add("bludgeoning");
|
||||
monster.damageVulnerabilities.add("necrotic");
|
||||
// Condition Immunities
|
||||
monster.conditionImmunities.add("blinded");
|
||||
// Senses
|
||||
monster.senses.add("blindsight 10 ft. (blind beyond this range)");
|
||||
monster.senses.add("darkvision 20 ft.");
|
||||
monster.senses.add("tremorsense 30 ft.");
|
||||
monster.senses.add("truesight 40 ft.");
|
||||
monster.telepathyRange = 20;
|
||||
monster.understandsButDescription = "doesn't care";
|
||||
// Languages
|
||||
monster.languages.add(new Language("English", true));
|
||||
monster.languages.add(new Language("Steve", false));
|
||||
monster.languages.add(new Language("Spanish", true));
|
||||
monster.languages.add(new Language("French", true));
|
||||
monster.languages.add(new Language("Mermataur", false));
|
||||
monster.languages.add(new Language("Goldfish", false));
|
||||
// Challenge Rating
|
||||
monster.challengeRating = ChallengeRating.CUSTOM;
|
||||
monster.customChallengeRatingDescription = "Infinite (0XP)";
|
||||
monster.customProficiencyBonus = 4;
|
||||
// Abilities
|
||||
monster.abilities.add(new Trait("Spellcasting", "The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC [WIS SAVE], [WIS ATK] to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n\n> Cantrips (at will): _light, sacred flame, thaumaturgy_\n> 1st level (3 slots): _bless, cure wounds, sanctuary_"));
|
||||
monster.abilities.add(new Trait("Amphibious", "The dragon can breathe air and water."));
|
||||
monster.abilities.add(new Trait("Legendary Resistance (3/Day)", "If the dragon fails a saving throw, it can choose to succeed instead."));
|
||||
// Actions
|
||||
monster.actions.add(new Trait("Club", "_Melee Weapon Attack:_ [STR ATK] to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."));
|
||||
|
||||
return monster;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.majinnaibu.monstercards.data;
|
||||
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
|
||||
@Dao
|
||||
public interface MonsterDAO {
|
||||
@Query("SELECT * FROM monsters")
|
||||
Flowable<List<Monster>> getAll();
|
||||
|
||||
@Query("SELECT * FROM monsters WHERE id IN (:monsterIds)")
|
||||
Flowable<List<Monster>> loadAllByIds(String[] monsterIds);
|
||||
|
||||
@Query("SELECT * FROM monsters WHERE name LIKE :name LIMIT 1")
|
||||
Flowable<Monster> findByName(String name);
|
||||
|
||||
@Query("SELECT monsters.* FROM monsters JOIN monsters_fts ON monsters.oid = monsters_fts.docid WHERE monsters_fts MATCH :searchText")
|
||||
Flowable<List<Monster>> search(String searchText);
|
||||
|
||||
@Insert
|
||||
Completable insertAll(Monster... monsters);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
Completable save(Monster... monsters);
|
||||
|
||||
@Delete
|
||||
Completable delete(Monster monster);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.majinnaibu.monstercards.data;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.majinnaibu.monstercards.AppDatabase;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Completable;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MonsterRepository {
|
||||
|
||||
private AppDatabase m_db;
|
||||
|
||||
public MonsterRepository(@NonNull AppDatabase db) {
|
||||
m_db = db;
|
||||
}
|
||||
|
||||
public Flowable<List<Monster>> getMonsters() {
|
||||
|
||||
return m_db.monsterDAO()
|
||||
.getAll()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Flowable<List<Monster>> searchMonsters(String searchText) {
|
||||
return m_db.monsterDAO()
|
||||
.search(searchText)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Flowable<Monster> getMonster(UUID monsterId) {
|
||||
return m_db.monsterDAO()
|
||||
.loadAllByIds(new String[]{monsterId.toString()})
|
||||
.map(
|
||||
monsters -> {
|
||||
if (monsters.size() > 0) {
|
||||
return monsters.get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Completable addMonster(Monster monster) {
|
||||
Completable result = m_db.monsterDAO().insertAll(monster);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable deleteMonster(Monster monster) {
|
||||
Completable result = m_db.monsterDAO().delete(monster);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Completable saveMonster(Monster monster) {
|
||||
Completable result = m_db.monsterDAO().save(monster);
|
||||
result.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
|
||||
public class ArmorTypeConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromArmorType(ArmorType armorType) {
|
||||
return armorType.stringValue;
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static ArmorType armorTypeFromStringValue(String stringValue) {
|
||||
return ArmorType.valueOfString(stringValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
|
||||
public class ChallengeRatingConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromChallengeRating(ChallengeRating challengeRating) {
|
||||
return challengeRating.stringValue;
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static ChallengeRating challengeRatingFromStringValue(String stringValue) {
|
||||
return ChallengeRating.valueOfString(stringValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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.Trait;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ListOfTraitsConverter {
|
||||
@TypeConverter
|
||||
public static String fromListOfTraits(List<Trait> traits) {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(traits);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static List<Trait> listOfTraitsFromString(String string) {
|
||||
Gson gson = new Gson();
|
||||
Type setType = new TypeToken<ArrayList<Trait>>() {
|
||||
}.getType();
|
||||
return gson.fromJson(string, setType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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.Language;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SetOfLanguageConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromSetOfLanguage(Set<Language> languages) {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(languages);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Set<Language> setOfLanguageFromString(String string) {
|
||||
Gson gson = new Gson();
|
||||
Type setType = new TypeToken<HashSet<Language>>() {
|
||||
}.getType();
|
||||
return gson.fromJson(string, setType);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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.Skill;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SetOfSkillConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromSetOfSkill(Set<Skill> skills) {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(skills);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Set<Skill> setOfSkillFromString(String string) {
|
||||
Gson gson = new Gson();
|
||||
Type setType = new TypeToken<HashSet<Skill>>() {
|
||||
}.getType();
|
||||
return gson.fromJson(string, setType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SetOfStringConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromSetOfString(Set<String> strings) {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(strings);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Set<String> setOfStringFromString(String string) {
|
||||
Gson gson = new Gson();
|
||||
Type setType = new TypeToken<HashSet<String>>() {
|
||||
}.getType();
|
||||
return gson.fromJson(string, setType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromUUID(UUID uuid) {
|
||||
return uuid.toString();
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static UUID uuidFromString(String string) {
|
||||
return UUID.fromString(string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum AbilityScore {
|
||||
STRENGTH("strength", "Strength", "STR"),
|
||||
DEXTERITY("dexterity", "Dexterity", "DEX"),
|
||||
CONSTITUTION("constitution", "Constitution", "CON"),
|
||||
INTELLIGENCE("intellligence", "Intelligence", "INT"),
|
||||
WISDOM("wisdom", "Wisdom", "WIS"),
|
||||
CHARISMA("charisma", "Charisma", "CHA"),
|
||||
;
|
||||
|
||||
public final String displayName;
|
||||
public final String shortDisplayName;
|
||||
public final String stringValue;
|
||||
|
||||
AbilityScore(String stringValue, String displayName, String shortDisplayName) {
|
||||
this.displayName = displayName;
|
||||
this.stringValue = stringValue;
|
||||
this.shortDisplayName = shortDisplayName;
|
||||
}
|
||||
|
||||
public static AbilityScore valueOfString(String string) {
|
||||
for (AbilityScore abilityScore : values()) {
|
||||
if (abilityScore.stringValue.equals(string)) {
|
||||
return abilityScore;
|
||||
}
|
||||
}
|
||||
return AbilityScore.STRENGTH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
public enum AdvantageType {
|
||||
NONE("none", "None", ""),
|
||||
ADVANTAGE("advantage", "Advantage", "A"),
|
||||
DISADVANTAGE("disadvantage", "Disadvantage", "D"),
|
||||
;
|
||||
|
||||
public final String displayName;
|
||||
public final String stringValue;
|
||||
public final String label;
|
||||
|
||||
AdvantageType(String stringValue, String displayName, String label) {
|
||||
this.displayName = displayName;
|
||||
this.stringValue = stringValue;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public static AdvantageType valueOfString(String string) {
|
||||
for (AdvantageType advantageType : values()) {
|
||||
if (advantageType.stringValue.equals(string)) {
|
||||
return advantageType;
|
||||
}
|
||||
}
|
||||
return AdvantageType.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum ArmorType {
|
||||
NONE("none", "None", 10),
|
||||
NATURAL_ARMOR("natural armor", "Natural Armor", 10),
|
||||
MAGE_ARMOR("mage armor", "Mage Armor", 10),
|
||||
PADDED("padded", "Padded", 11),
|
||||
LEATHER("leather", "Leather", 11),
|
||||
STUDDED_LEATHER("studded", "Studded Leather", 12),
|
||||
HIDE("hide", "Hide", 12),
|
||||
CHAIN_SHIRT("chain shirt", "Chain Shirt", 13),
|
||||
SCALE_MAIL("scale mail", "Scale Mail", 14),
|
||||
BREASTPLATE("breastplate", "Breastplate", 14),
|
||||
HALF_PLATE("half plate", "Half Plate", 15),
|
||||
RING_MAIL("ring mail", "Ring Mail", 14),
|
||||
CHAIN_MAIL("chain mail", "Chain Mail", 16),
|
||||
SPLINT_MAIL("splint", "Splint Mail", 17),
|
||||
PLATE_MAIL("plate", "Plate Mail", 18),
|
||||
OTHER("other", "Other", 10),
|
||||
;
|
||||
|
||||
public final String displayName;
|
||||
public final String stringValue;
|
||||
public final int baseArmorClass;
|
||||
|
||||
ArmorType(String stringValue, String displayName, int baseArmorClass) {
|
||||
this.displayName = displayName;
|
||||
this.stringValue = stringValue;
|
||||
this.baseArmorClass = baseArmorClass;
|
||||
}
|
||||
|
||||
public static ArmorType valueOfString(String string) {
|
||||
for (ArmorType armorType : values()) {
|
||||
if (armorType.stringValue.equals(string)) {
|
||||
return armorType;
|
||||
}
|
||||
}
|
||||
return ArmorType.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum ChallengeRating {
|
||||
CUSTOM("custom", "Custom", 0),
|
||||
ZERO("zero", "0 (10 XP)", 2),
|
||||
ONE_EIGHTH("1/8", "1/8 (25 XP)", 2),
|
||||
ONE_QUARTER("1/4", "1/4 (50 XP)", 2),
|
||||
ONE_HALF("1/2", "1/2 (100 XP)", 2),
|
||||
ONE("1", "1 (200 XP)", 2),
|
||||
TWO("2", "2 (450 XP)", 2),
|
||||
THREE("3", "3 (700 XP)", 2),
|
||||
FOUR("4", "4 (1,100 XP)", 2),
|
||||
FIVE("5", "5 (1,800 XP)", 3),
|
||||
SIX("6", "6 (2,300 XP)", 3),
|
||||
SEVEN("7", "7 (2,900 XP)", 3),
|
||||
EIGHT("8", "8 (3,900 XP)", 3),
|
||||
NINE("9", "9 (5,000 XP)", 4),
|
||||
TEN("10", "10 (5,900 XP)", 4),
|
||||
ELEVEN("11", "11 (7,200 XP)", 4),
|
||||
TWELVE("12", "12 (8,400 XP)", 4),
|
||||
THIRTEEN("13", "13 (10,000 XP)", 5),
|
||||
FOURTEEN("14", "14 (11,500 XP)", 5),
|
||||
FIFTEEN("15", "15 (13,000 XP)", 5),
|
||||
SIXTEEN("16", "16 (15,000 XP)", 5),
|
||||
SEVENTEEN("17", "17 (18,000 XP)", 6),
|
||||
EIGHTEEN("18", "18 (20,000 XP)", 6),
|
||||
NINETEEN("19", "19 (22,000 XP)", 6),
|
||||
TWENTY("20", "20 (25,000 XP)", 6),
|
||||
TWENTY_ONE("21", "21 (33,000 XP)", 7),
|
||||
TWENTY_TWO("22", "22 (41,000 XP)", 7),
|
||||
TWENTY_THREE("23", "23 (50,000 XP)", 7),
|
||||
TWENTY_FOUR("24", "24 (62,000 XP)", 7),
|
||||
TWENTY_FIVE("25", "25 (75,000 XP)", 8),
|
||||
TWENTY_SIX("26", "26 (90,000 XP)", 8),
|
||||
TWENTY_SEVEN("27", "27 (105,000 XP)", 8),
|
||||
TWENTY_EIGHT("28", "28 (120,000 XP)", 8),
|
||||
TWENTY_NINE("29", "29 (135,000 XP)", 9),
|
||||
THIRTY("30", "30 (155,000 XP)", 9),
|
||||
;
|
||||
|
||||
public final String displayName;
|
||||
public final String stringValue;
|
||||
public final int proficiencyBonus;
|
||||
|
||||
ChallengeRating(String stringValue, String displayName, int proficiencyBonus) {
|
||||
this.displayName = displayName;
|
||||
this.stringValue = stringValue;
|
||||
this.proficiencyBonus = proficiencyBonus;
|
||||
}
|
||||
|
||||
public static ChallengeRating valueOfString(String string) {
|
||||
for (ChallengeRating challengeRating : values()) {
|
||||
if (challengeRating.stringValue.equals(string)) {
|
||||
return challengeRating;
|
||||
}
|
||||
}
|
||||
return ChallengeRating.ONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
public enum ProficiencyType {
|
||||
NONE("none", "None", ""),
|
||||
PROFICIENT("proficient", "Proficient", "P"),
|
||||
EXPERTISE("experties", "Expertise", "Ex"),
|
||||
;
|
||||
|
||||
public final String displayName;
|
||||
public final String stringValue;
|
||||
public final String label;
|
||||
|
||||
ProficiencyType(String stringValue, String displayName, String label) {
|
||||
this.displayName = displayName;
|
||||
this.stringValue = stringValue;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public static ProficiencyType valueOfString(String string) {
|
||||
for (ProficiencyType proficiencyType : values()) {
|
||||
if (proficiencyType.stringValue.equals(string)) {
|
||||
return proficiencyType;
|
||||
}
|
||||
}
|
||||
return ProficiencyType.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
public enum StringType {
|
||||
CONDITION_IMMUNITY,
|
||||
DAMAGE_IMMUNITY,
|
||||
DAMAGE_RESISTANCE,
|
||||
DAMAGE_VULNERABILITY,
|
||||
SENSE
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.majinnaibu.monstercards.data.enums;
|
||||
|
||||
public enum TraitType {
|
||||
ABILITY,
|
||||
ACTION,
|
||||
LAIR_ACTION,
|
||||
LEGENDARY_ACTION,
|
||||
REGIONAL_ACTION,
|
||||
REACTIONS
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ArrayHelper {
|
||||
public static int indexOf(Object[] array, Object target) {
|
||||
for (int index = 0; index < array.length; index++) {
|
||||
if (Objects.equals(array[index], target)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
import org.commonmark.node.Document;
|
||||
import org.commonmark.node.Node;
|
||||
import org.commonmark.node.Paragraph;
|
||||
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;
|
||||
|
||||
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) {
|
||||
Parser parser = Parser.builder().build();
|
||||
Node document = parser.parse(rawCommonMark);
|
||||
Node parent1 = document.getFirstChild();
|
||||
Node parent2 = document.getLastChild();
|
||||
if (parent1 == parent2 && parent1 instanceof Paragraph) {
|
||||
document = new Document();
|
||||
Node child = parent1.getFirstChild();
|
||||
while(child != null) {
|
||||
Node nextChild = child.getNext();
|
||||
document.appendChild(child);
|
||||
child = nextChild;//child.getNext();
|
||||
}
|
||||
}
|
||||
HtmlRenderer renderer = HtmlRenderer.builder().build();
|
||||
return renderer.render(document);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.majinnaibu.monstercards.data.converters.ArmorTypeConverter;
|
||||
import com.majinnaibu.monstercards.data.converters.ChallengeRatingConverter;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class MonsterImportHelper {
|
||||
public static Monster fromJSON(String json) {
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonObject rootDict = parser.parse(json).getAsJsonObject();
|
||||
|
||||
Monster monster = new Monster();
|
||||
monster.name = Helpers.getString(rootDict, "name");
|
||||
monster.size = Helpers.getString(rootDict, "size");
|
||||
monster.type = Helpers.getString(rootDict, "type");
|
||||
monster.subtype = Helpers.getString(rootDict, "tag");
|
||||
monster.alignment = Helpers.getString(rootDict, "alignment");
|
||||
monster.hitDice = Helpers.getInt(rootDict, "hitDice");
|
||||
monster.armorType = ArmorTypeConverter.armorTypeFromStringValue(Helpers.getString(rootDict, "armorName"));
|
||||
monster.shieldBonus = Helpers.getInt(rootDict, "shieldBonus");
|
||||
monster.naturalArmorBonus = Helpers.getInt(rootDict, "natArmorBonus");
|
||||
monster.otherArmorDescription = Helpers.getString(rootDict, "otherArmorDesc");
|
||||
monster.walkSpeed = Helpers.getInt(rootDict, "speed");
|
||||
monster.burrowSpeed = Helpers.getInt(rootDict, "burrowSpeed");
|
||||
monster.climbSpeed = Helpers.getInt(rootDict, "climbSpeed");
|
||||
monster.flySpeed = Helpers.getInt(rootDict, "flySpeed");
|
||||
monster.canHover = Helpers.getBool(rootDict, "hover");
|
||||
monster.swimSpeed = Helpers.getInt(rootDict, "swimSpeed");
|
||||
monster.hasCustomHP = Helpers.getBool(rootDict, "customHP");
|
||||
monster.hasCustomSpeed = Helpers.getBool(rootDict, "customSpeed");
|
||||
monster.customHPDescription = Helpers.getString(rootDict, "hpText");
|
||||
monster.customSpeedDescription = Helpers.getString(rootDict, "speedDesc");
|
||||
monster.strengthScore = Helpers.getInt(rootDict, "strPoints");
|
||||
monster.dexterityScore = Helpers.getInt(rootDict, "dexPoints");
|
||||
monster.constitutionScore = Helpers.getInt(rootDict, "conPoints");
|
||||
monster.intelligenceScore = Helpers.getInt(rootDict, "intPoints");
|
||||
monster.wisdomScore = Helpers.getInt(rootDict, "wisPoints");
|
||||
monster.charismaScore = Helpers.getInt(rootDict, "chaPoints");
|
||||
Helpers.addSense(monster, rootDict, "blindsight");
|
||||
// Helpers.getBool(rootDict, "blind");
|
||||
Helpers.addSense(monster, rootDict, "darkvision");
|
||||
Helpers.addSense(monster, rootDict, "tremorsense");
|
||||
Helpers.addSense(monster, rootDict, "truesight");
|
||||
monster.telepathyRange = Helpers.getInt(rootDict, "telepathy");
|
||||
monster.challengeRating = ChallengeRatingConverter.challengeRatingFromStringValue(Helpers.getString(rootDict, "cr"));
|
||||
monster.customChallengeRatingDescription = Helpers.getString(rootDict, "customCr");
|
||||
monster.customProficiencyBonus = Helpers.getInt(rootDict, "customProf");
|
||||
// Helpers.getBool(rootDict, "isLegendary");
|
||||
// Helpers.getString(rootDict, "legendariesDescription");
|
||||
// Helpers.getBool(rootDict, "isLair");
|
||||
// Helpers.getString(rootDict, "lairDescription");
|
||||
// Helpers.getString(rootDict, "lairDescriptionEnd");
|
||||
// Helpers.getBool(rootDict, "isRegional");
|
||||
// Helpers.getString(rootDict, "regionalDescription");
|
||||
// Helpers.getString(rootDict, "regionalDescriptionEnd");
|
||||
// properties: []
|
||||
monster.abilities = Helpers.getListOfTraits(rootDict, "abilities");
|
||||
monster.actions = Helpers.getListOfTraits(rootDict, "actions");
|
||||
monster.reactions = Helpers.getListOfTraits(rootDict, "reactions");
|
||||
monster.legendaryActions = Helpers.getListOfTraits(rootDict, "legendaries");
|
||||
monster.lairActions = Helpers.getListOfTraits(rootDict, "lairs");
|
||||
monster.regionalActions = Helpers.getListOfTraits(rootDict, "regionals");
|
||||
Helpers.addSavingThrows(monster, rootDict);
|
||||
// skills: []
|
||||
monster.skills = Helpers.getSetOfSkills(rootDict);
|
||||
// damagetypes: []
|
||||
// specialdamage: []
|
||||
monster.damageImmunities = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "i");
|
||||
monster.damageImmunities.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "i"));
|
||||
monster.damageResistances = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "r");
|
||||
monster.damageResistances.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "r"));
|
||||
monster.damageVulnerabilities = Helpers.getSetOfDamageTypes(rootDict, "damageTypes", "v");
|
||||
monster.damageVulnerabilities.addAll(Helpers.getSetOfDamageTypes(rootDict, "specialdamage", "v"));
|
||||
// conditions: []
|
||||
monster.conditionImmunities = Helpers.getSetOfDamageTypes(rootDict, "conditions");
|
||||
// languages: []
|
||||
monster.languages = Helpers.getSetOfLanguages(rootDict, "languages");
|
||||
// understandsBut: ""
|
||||
monster.understandsButDescription = Helpers.getString(rootDict, "understandsBut");
|
||||
// shortName: ""
|
||||
// doubleColumns: true
|
||||
// separationPoint: -1
|
||||
// damage: []
|
||||
// pluralName: ""
|
||||
|
||||
return monster;
|
||||
}
|
||||
|
||||
|
||||
public static class Helpers {
|
||||
public static String getString(JsonObject dict, String name) {
|
||||
return getString(dict, name, "");
|
||||
}
|
||||
|
||||
public static String getString(@NotNull JsonObject dict, String name, String defaultValue) {
|
||||
if (dict.has(name)) {
|
||||
return dict.get(name).getAsString();
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int getInt(JsonObject dict, String name) {
|
||||
return getInt(dict, name, 0);
|
||||
}
|
||||
|
||||
public static int getInt(@NotNull JsonObject dict, String name, int defaultValue) {
|
||||
if (dict.has(name)) {
|
||||
JsonElement element = dict.get(name);
|
||||
if (element.isJsonPrimitive()) {
|
||||
JsonPrimitive rawValue = element.getAsJsonPrimitive();//dict.getAsJsonPrimitive(name);
|
||||
if (rawValue.isNumber()) {
|
||||
return rawValue.getAsInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static boolean getBool(JsonObject dict, String name) {
|
||||
return getBool(dict, name, false);
|
||||
}
|
||||
|
||||
public static boolean getBool(@NotNull JsonObject dict, String name, boolean defaultValue) {
|
||||
if (dict.has(name)) {
|
||||
JsonElement element = dict.get(name);
|
||||
if (element.isJsonPrimitive()) {
|
||||
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
||||
if (rawValue.isBoolean()) {
|
||||
return rawValue.getAsBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String formatDistance(String name, int distance) {
|
||||
return String.format("%s %d ft.", name, distance);
|
||||
}
|
||||
|
||||
public static void addSense(Monster monster, JsonObject root, String name) {
|
||||
int distance = Helpers.getInt(root, name);
|
||||
if (distance > 0) {
|
||||
monster.senses.add(Helpers.formatDistance(name, distance));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Trait> getListOfTraits(@NotNull JsonObject dict, String name) {
|
||||
ArrayList<Trait> traits = new ArrayList<>();
|
||||
if (dict.has(name)) {
|
||||
JsonElement arrayElement = dict.get(name);
|
||||
if (arrayElement.isJsonArray()) {
|
||||
JsonArray array = arrayElement.getAsJsonArray();
|
||||
int size = array.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
JsonElement jsonElement = array.get(index);
|
||||
if (jsonElement.isJsonObject()) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
String traitName = Helpers.getString(jsonObject, "name");
|
||||
String description = Helpers.getString(jsonObject, "description");
|
||||
Trait trait = new Trait(traitName, description);
|
||||
traits.add(trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
||||
public static void addSavingThrows(Monster monster, JsonObject root) {
|
||||
if (root.has("sthrows")) {
|
||||
JsonElement arrayElement = root.get("sthrows");
|
||||
if (arrayElement.isJsonArray()) {
|
||||
JsonArray array = arrayElement.getAsJsonArray();
|
||||
int size = array.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
JsonElement jsonElement = array.get(index);
|
||||
if (jsonElement.isJsonObject()) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
String name = Helpers.getString(jsonObject, "name");
|
||||
if ("str".equals(name)) {
|
||||
monster.strengthSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
} else if ("dex".equals(name)) {
|
||||
monster.dexteritySavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
} else if ("con".equals(name)) {
|
||||
monster.constitutionSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
} else if ("int".equals(name)) {
|
||||
monster.intelligenceSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
} else if ("wis".equals(name)) {
|
||||
monster.wisdomSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
} else if ("cha".equals(name)) {
|
||||
monster.charismaSavingThrowProficiency = ProficiencyType.PROFICIENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<Skill> getSetOfSkills(JsonObject root) {
|
||||
HashSet<Skill> skills = new HashSet<>();
|
||||
if (root.has("skills")) {
|
||||
JsonElement arrayElement = root.get("skills");
|
||||
if (arrayElement.isJsonArray()) {
|
||||
JsonArray array = arrayElement.getAsJsonArray();
|
||||
int size = array.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
JsonElement jsonElement = array.get(index);
|
||||
if (jsonElement.isJsonObject()) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
String name = Helpers.getString(jsonObject, "name");
|
||||
String stat = Helpers.getString(jsonObject, "stat");
|
||||
String note = Helpers.getString(jsonObject, "note");
|
||||
|
||||
Skill skill = new Skill(name, AbilityScore.valueOfString(stat), AdvantageType.NONE, " (ex)".equals(note) ? ProficiencyType.EXPERTISE : ProficiencyType.PROFICIENT);
|
||||
skills.add(skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return skills;
|
||||
}
|
||||
|
||||
public static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) {
|
||||
return getSetOfDamageTypes(rootDict, name, null);
|
||||
}
|
||||
|
||||
public static Set<String> getSetOfDamageTypes(JsonObject root, String name, String type) {
|
||||
HashSet<String> damageTypes = new HashSet<>();
|
||||
if (root.has(name)) {
|
||||
JsonElement arrayElement = root.get(name);
|
||||
if (arrayElement.isJsonArray()) {
|
||||
JsonArray array = arrayElement.getAsJsonArray();
|
||||
int size = array.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
JsonElement jsonElement = array.get(index);
|
||||
if (jsonElement.isJsonObject()) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
String dtName = Helpers.getString(jsonObject, "name");
|
||||
String dtType = Helpers.getString(jsonObject, "type");
|
||||
if (type == null || type.equals(dtType)) {
|
||||
damageTypes.add(dtName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return damageTypes;
|
||||
}
|
||||
|
||||
public static Set<Language> getSetOfLanguages(JsonObject root, String name) {
|
||||
HashSet<Language> languages = new HashSet<>();
|
||||
if (root.has(name)) {
|
||||
JsonElement arrayElement = root.get(name);
|
||||
if (arrayElement.isJsonArray()) {
|
||||
JsonArray array = arrayElement.getAsJsonArray();
|
||||
int size = array.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
JsonElement jsonElement = array.get(index);
|
||||
if (jsonElement.isJsonObject()) {
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
String languageName = Helpers.getString(jsonObject, "name");
|
||||
boolean canSpeak = Helpers.getBool(jsonObject, "speaks");
|
||||
Language language = new Language(languageName, canSpeak);
|
||||
languages.add(language);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
@SuppressWarnings({"BooleanMethodIsAlwaysInverted", "RedundantIfStatement"})
|
||||
public final class StringHelper {
|
||||
public static boolean isNullOrEmpty(CharSequence value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("".contentEquals(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class Language implements Comparator<Language>, Comparable<Language> {
|
||||
|
||||
public Language(String name, boolean speaks) {
|
||||
mName = name;
|
||||
mSpeaks = speaks;
|
||||
}
|
||||
|
||||
private String mName;
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
public void setName(String value) {
|
||||
mName = value;
|
||||
}
|
||||
|
||||
private boolean mSpeaks;
|
||||
public boolean getSpeaks() {
|
||||
return mSpeaks;
|
||||
}
|
||||
public void setSpeaks(boolean value) {
|
||||
mSpeaks = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Language o) {
|
||||
return this.getName().compareToIgnoreCase(o.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Language o1, Language o2) {
|
||||
return o1.getName().compareToIgnoreCase(o2.getName());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Fts4;
|
||||
|
||||
@Entity(tableName = "monsters_fts")
|
||||
@Fts4(contentEntity = Monster.class)
|
||||
public class MonsterFTS {
|
||||
public String name;
|
||||
public String size;
|
||||
public String type;
|
||||
public String subtype;
|
||||
public String alignment;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public class Skill implements Comparator<Skill>, Comparable<Skill> {
|
||||
|
||||
public String name;
|
||||
public AbilityScore abilityScore;
|
||||
public AdvantageType advantageType;
|
||||
public ProficiencyType proficiencyType;
|
||||
|
||||
public Skill(String name, AbilityScore abilityScore) {
|
||||
this(name, abilityScore, AdvantageType.NONE, ProficiencyType.PROFICIENT);
|
||||
}
|
||||
|
||||
public Skill(String name, AbilityScore abilityScore, AdvantageType advantageType) {
|
||||
this(name, abilityScore, advantageType, ProficiencyType.PROFICIENT);
|
||||
}
|
||||
|
||||
public Skill(String name, AbilityScore abilityScore, AdvantageType advantageType, ProficiencyType proficiencyType) {
|
||||
this.name = name;
|
||||
this.abilityScore = abilityScore;
|
||||
this.advantageType = advantageType;
|
||||
this.proficiencyType = proficiencyType;
|
||||
}
|
||||
|
||||
public int getSkillBonus(Monster monster) {
|
||||
int modifier = monster.getAbilityModifier(abilityScore);
|
||||
switch (proficiencyType) {
|
||||
case PROFICIENT:
|
||||
return modifier + monster.getProficiencyBonus();
|
||||
case EXPERTISE:
|
||||
return modifier + monster.getProficiencyBonus() * 2;
|
||||
case NONE:
|
||||
default:
|
||||
return modifier;
|
||||
}
|
||||
}
|
||||
|
||||
public String getText(Monster monster) {
|
||||
int bonus = getSkillBonus(monster);
|
||||
|
||||
return String.format(
|
||||
"%s%s %+d%s",
|
||||
name.substring(0, 1),
|
||||
name.substring(1),
|
||||
bonus,
|
||||
advantageType == AdvantageType.ADVANTAGE ? " A" : advantageType == AdvantageType.DISADVANTAGE ? " D" : ""
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Skill o) {
|
||||
return this.name.compareToIgnoreCase(o.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Skill o1, Skill o2) {
|
||||
return o1.name.compareToIgnoreCase(o2.name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
public class Trait {
|
||||
|
||||
public String name;
|
||||
public String description;
|
||||
|
||||
public Trait(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class CollectionsFragment extends MCFragment {
|
||||
|
||||
private CollectionsViewModel collectionsViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
collectionsViewModel = new ViewModelProvider(this).get(CollectionsViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
||||
final TextView textView = root.findViewById(R.id.text_collections);
|
||||
collectionsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable String s) {
|
||||
textView.setText(s);
|
||||
}
|
||||
});
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class CollectionsViewModel extends ViewModel {
|
||||
|
||||
private MutableLiveData<String> mText;
|
||||
|
||||
public CollectionsViewModel() {
|
||||
mText = new MutableLiveData<>();
|
||||
mText.setValue("This is collections fragment");
|
||||
}
|
||||
|
||||
public LiveData<String> getText() {
|
||||
return mText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class AbilityScorePicker extends LinearLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private AbilityScore mSelectedValue;
|
||||
private String mLabel;
|
||||
|
||||
public AbilityScorePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = AbilityScore.STRENGTH;
|
||||
mOnValueChangedListener = null;
|
||||
// TODO: use this as default but allow setting via attribute
|
||||
mLabel = "Ability Score";
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Stepper, 0, 0);
|
||||
String label = a.getString(R.styleable.Stepper_label);
|
||||
if (label != null) {
|
||||
mLabel = label;
|
||||
}
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_ability_score_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.label.setText(mLabel);
|
||||
|
||||
mHolder.spinner.setAdapter(new ArrayAdapter<AbilityScore>(getContext(), R.layout.dropdown_list_item, AbilityScore.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
AbilityScore item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
AbilityScore item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mSelectedValue = (AbilityScore) parent.getItemAtPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mSelectedValue = AbilityScore.STRENGTH;
|
||||
}
|
||||
});
|
||||
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
|
||||
|
||||
setValue(AbilityScore.STRENGTH);
|
||||
// TODO: listen for changes on the component to update mSelectedValue;
|
||||
}
|
||||
|
||||
public AbilityScorePicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AbilityScore getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(AbilityScore value) {
|
||||
if (value != mSelectedValue) {
|
||||
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
if (!Objects.equals(mLabel, label)) {
|
||||
mLabel = label;
|
||||
mHolder.label.setText(label);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(AbilityScore value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
private final Spinner spinner;
|
||||
private final TextView label;
|
||||
|
||||
ViewHolder(View root) {
|
||||
spinner = root.findViewById(R.id.spinner);
|
||||
label = root.findViewById(R.id.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AdvantagePicker extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private AdvantageType mSelectedValue;
|
||||
|
||||
public AdvantagePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = AdvantageType.NONE;
|
||||
mOnValueChangedListener = null;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_advantage_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(AdvantageType.NONE);
|
||||
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (R.id.advantage == checkedId) {
|
||||
setValue(AdvantageType.ADVANTAGE);
|
||||
} else if (R.id.disadvantage == checkedId) {
|
||||
setValue(AdvantageType.DISADVANTAGE);
|
||||
} else {
|
||||
setValue(AdvantageType.NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AdvantagePicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AdvantageType getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(AdvantageType value) {
|
||||
if (mSelectedValue != value) {
|
||||
mSelectedValue = value;
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(mSelectedValue);
|
||||
}
|
||||
}
|
||||
final int checkedId = mHolder.group.getCheckedRadioButtonId();
|
||||
if (mSelectedValue == AdvantageType.ADVANTAGE) {
|
||||
if (checkedId != R.id.advantage) {
|
||||
mHolder.advantage.setChecked(true);
|
||||
}
|
||||
} else if (mSelectedValue == AdvantageType.DISADVANTAGE) {
|
||||
if (checkedId != R.id.disadvantage) {
|
||||
mHolder.disadvantage.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
if (checkedId != R.id.none) {
|
||||
mHolder.none.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(AdvantageType value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RadioGroup group;
|
||||
final MaterialRadioButton none;
|
||||
final MaterialRadioButton advantage;
|
||||
final MaterialRadioButton disadvantage;
|
||||
|
||||
ViewHolder(View root) {
|
||||
group = root.findViewById(R.id.group);
|
||||
none = root.findViewById(R.id.none);
|
||||
advantage = root.findViewById(R.id.advantage);
|
||||
disadvantage = root.findViewById(R.id.disadvantage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ProficiencyPicker extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private ProficiencyType mSelectedValue;
|
||||
|
||||
public ProficiencyPicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = ProficiencyType.NONE;
|
||||
mOnValueChangedListener = null;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_proficiency_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(ProficiencyType.NONE);
|
||||
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (R.id.proficient == checkedId) {
|
||||
setValue(ProficiencyType.PROFICIENT);
|
||||
} else if (R.id.expertise == checkedId) {
|
||||
setValue(ProficiencyType.EXPERTISE);
|
||||
} else {
|
||||
setValue(ProficiencyType.NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ProficiencyPicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ProficiencyType getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(ProficiencyType value) {
|
||||
if (mSelectedValue != value) {
|
||||
mSelectedValue = value;
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(mSelectedValue);
|
||||
}
|
||||
}
|
||||
final int checkedId = mHolder.group.getCheckedRadioButtonId();
|
||||
if (mSelectedValue == ProficiencyType.PROFICIENT) {
|
||||
if (checkedId != R.id.proficient) {
|
||||
mHolder.proficient.setChecked(true);
|
||||
}
|
||||
} else if (mSelectedValue == ProficiencyType.EXPERTISE) {
|
||||
if (checkedId != R.id.expertise) {
|
||||
mHolder.expertise.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
if (checkedId != R.id.none) {
|
||||
mHolder.none.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(ProficiencyType value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RadioGroup group;
|
||||
final MaterialRadioButton none;
|
||||
final MaterialRadioButton proficient;
|
||||
final MaterialRadioButton expertise;
|
||||
|
||||
ViewHolder(View root) {
|
||||
group = root.findViewById(R.id.group);
|
||||
none = root.findViewById(R.id.none);
|
||||
proficient = root.findViewById(R.id.proficient);
|
||||
expertise = root.findViewById(R.id.expertise);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class Stepper extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private int mCurrentValue;
|
||||
private int mStep;
|
||||
private int mMinValue;
|
||||
private int mMaxValue;
|
||||
private String mLabel;
|
||||
private OnValueChangeListener mOnValueChangeListener;
|
||||
private OnFormatValueCallback mOnFormatValueCallback;
|
||||
|
||||
public Stepper(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mCurrentValue = 0;
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Stepper, 0, 0);
|
||||
mStep = a.getInt(R.styleable.Stepper_stepAmount, 1);
|
||||
mMinValue = a.getInt(R.styleable.Stepper_minValue, Integer.MIN_VALUE);
|
||||
mMaxValue = a.getInt(R.styleable.Stepper_maxValue, Integer.MAX_VALUE);
|
||||
mLabel = a.getString(R.styleable.Stepper_label);
|
||||
a.recycle();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_stepper, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(mCurrentValue);
|
||||
mHolder.increment.setOnClickListener(v -> setValue(mCurrentValue + mStep));
|
||||
mHolder.decrement.setOnClickListener(v -> setValue(mCurrentValue - mStep));
|
||||
|
||||
mHolder.label.setText(mLabel);
|
||||
}
|
||||
|
||||
public Stepper(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public void setLabel(String newLabel) {
|
||||
if (!Objects.equals(mLabel, newLabel)) {
|
||||
mLabel = newLabel;
|
||||
mHolder.label.setText(mLabel);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return mCurrentValue;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
int oldValue = this.mCurrentValue;
|
||||
int newValue = Math.min(mMaxValue, Math.max(mMinValue, value));
|
||||
Logger.logDebug(String.format("Setting stepper value value: %d, oldValue: %d, newValue: %d", value, oldValue, newValue));
|
||||
if (newValue != oldValue) {
|
||||
this.mCurrentValue = newValue;
|
||||
if (mOnValueChangeListener != null) {
|
||||
mOnValueChangeListener.onChange(newValue, oldValue);
|
||||
}
|
||||
if (mOnFormatValueCallback != null) {
|
||||
mHolder.text.setText(mOnFormatValueCallback.onFormatValue(this.mCurrentValue));
|
||||
} else {
|
||||
mHolder.text.setText(String.valueOf(this.mCurrentValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangeListener(OnValueChangeListener listener) {
|
||||
mOnValueChangeListener = listener;
|
||||
}
|
||||
|
||||
public void setOnFormatValueCallback(OnFormatValueCallback callback) {
|
||||
mOnFormatValueCallback = callback;
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return mStep;
|
||||
}
|
||||
|
||||
public void setStep(int step) {
|
||||
this.mStep = step;
|
||||
}
|
||||
|
||||
public int getMinValue() {
|
||||
return mMinValue;
|
||||
}
|
||||
|
||||
public void setMinValue(int minValue) {
|
||||
this.mMinValue = minValue;
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
public void setMaxValue(int maxValue) {
|
||||
this.mMaxValue = maxValue;
|
||||
}
|
||||
|
||||
public interface OnValueChangeListener {
|
||||
void onChange(int value, int previousValue);
|
||||
}
|
||||
|
||||
public interface OnFormatValueCallback {
|
||||
String onFormatValue(int value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final TextView text;
|
||||
final TextView label;
|
||||
final Button increment;
|
||||
final Button decrement;
|
||||
|
||||
ViewHolder(View root) {
|
||||
text = root.findViewById(R.id.text);
|
||||
label = root.findViewById(R.id.label);
|
||||
increment = root.findViewById(R.id.increment);
|
||||
decrement = root.findViewById(R.id.decrement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardFragment extends MCFragment {
|
||||
private DashboardViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private DashboardRecyclerViewAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setupRecyclerView(mHolder.list);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396);
|
||||
Context context = requireContext();
|
||||
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<Monster>> monsterData = mViewModel.getMonsters();
|
||||
mAdapter = new DashboardRecyclerViewAdapter(monster -> {
|
||||
if (monster != null) {
|
||||
navigateToMonsterDetail(monster);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
|
||||
}
|
||||
});
|
||||
if (monsterData != null) {
|
||||
monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters));
|
||||
}
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
private void navigateToMonsterDetail(Monster monster) {
|
||||
NavDirections action = DashboardFragmentDirections.actionNavigationDashboardToNavigationMonster(monster.id.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RecyclerView list;
|
||||
|
||||
ViewHolder(View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.databinding.CardMonsterBinding;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, DashboardRecyclerViewAdapter.ViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return Monster.areItemsTheSame(oldItem, newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return Monster.areContentsTheSame(oldItem, newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback<Monster> mOnClick;
|
||||
|
||||
protected DashboardRecyclerViewAdapter(ItemCallback<Monster> onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(CardMonsterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Monster monster = getItem(position);
|
||||
holder.monster = monster;
|
||||
holder.name.setText(monster.name);
|
||||
holder.meta.setText(monster.getMeta());
|
||||
holder.strengthAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.strengthSavingThrowAdvantage));
|
||||
holder.strengthModifier.setText(Helpers.getModifierString(monster.getStrengthModifier()));
|
||||
holder.strengthName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.STRENGTH));
|
||||
holder.strengthProficiency.setText(Helpers.getProficiencyAbbreviation(monster.strengthSavingThrowProficiency));
|
||||
|
||||
holder.dexterityAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.dexteritySavingThrowAdvantage));
|
||||
holder.dexterityModifier.setText(Helpers.getModifierString(monster.getDexterityModifier()));
|
||||
holder.dexterityName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.DEXTERITY));
|
||||
holder.dexterityProficiency.setText(Helpers.getProficiencyAbbreviation(monster.dexteritySavingThrowProficiency));
|
||||
|
||||
holder.constitutionAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.constitutionSavingThrowAdvantage));
|
||||
holder.constitutionModifier.setText(Helpers.getModifierString(monster.getConstitutionModifier()));
|
||||
holder.constitutionName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.CONSTITUTION));
|
||||
holder.constitutionProficiency.setText(Helpers.getProficiencyAbbreviation(monster.constitutionSavingThrowProficiency));
|
||||
|
||||
holder.intelligenceAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.intelligenceSavingThrowAdvantage));
|
||||
holder.intelligenceModifier.setText(Helpers.getModifierString(monster.getIntelligenceModifier()));
|
||||
holder.intelligenceName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.INTELLIGENCE));
|
||||
holder.intelligenceProficiency.setText(Helpers.getProficiencyAbbreviation(monster.intelligenceSavingThrowProficiency));
|
||||
|
||||
holder.wisdomAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.wisdomSavingThrowAdvantage));
|
||||
holder.wisdomModifier.setText(Helpers.getModifierString(monster.getWisdomModifier()));
|
||||
holder.wisdomName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.WISDOM));
|
||||
holder.wisdomProficiency.setText(Helpers.getProficiencyAbbreviation(monster.wisdomSavingThrowProficiency));
|
||||
|
||||
holder.charismaAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.charismaSavingThrowAdvantage));
|
||||
holder.charismaModifier.setText(Helpers.getModifierString(monster.getCharismaModifier()));
|
||||
holder.charismaName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.CHARISMA));
|
||||
holder.charismaProficiency.setText(Helpers.getProficiencyAbbreviation(monster.charismaSavingThrowProficiency));
|
||||
|
||||
holder.armorClass.setText(String.valueOf(monster.getArmorClassValue()));
|
||||
holder.hitPoints.setText(String.valueOf(monster.getHitPointsValue()));
|
||||
holder.challengeRating.setText(holder.challengeRating.getResources().getString(R.string.label_challenge_rating_with_value, Helpers.getChallengeRatingAbbreviation(monster.challengeRating)));
|
||||
|
||||
int numActions = monster.actions.size();
|
||||
if (numActions > 0) {
|
||||
holder.action1Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(0);
|
||||
holder.action1Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action1Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action1Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (numActions > 1) {
|
||||
holder.action2Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(1);
|
||||
holder.action2Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action2Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action2Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (numActions > 2) {
|
||||
holder.action3Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(2);
|
||||
holder.action3Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action3Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action3Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(holder.monster);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView name;
|
||||
public final TextView meta;
|
||||
public final View action1Group;
|
||||
public final TextView action1Name;
|
||||
public final TextView action1Description;
|
||||
public final View action2Group;
|
||||
public final TextView action2Name;
|
||||
public final TextView action2Description;
|
||||
public final View action3Group;
|
||||
public final TextView action3Name;
|
||||
public final TextView action3Description;
|
||||
public final TextView strengthName;
|
||||
public final TextView strengthModifier;
|
||||
public final TextView strengthProficiency;
|
||||
public final TextView strengthAdvantage;
|
||||
public final TextView dexterityName;
|
||||
public final TextView dexterityModifier;
|
||||
public final TextView dexterityProficiency;
|
||||
public final TextView dexterityAdvantage;
|
||||
public final TextView constitutionName;
|
||||
public final TextView constitutionModifier;
|
||||
public final TextView constitutionProficiency;
|
||||
public final TextView constitutionAdvantage;
|
||||
public final TextView intelligenceName;
|
||||
public final TextView intelligenceModifier;
|
||||
public final TextView intelligenceProficiency;
|
||||
public final TextView intelligenceAdvantage;
|
||||
public final TextView wisdomName;
|
||||
public final TextView wisdomModifier;
|
||||
public final TextView wisdomProficiency;
|
||||
public final TextView wisdomAdvantage;
|
||||
public final TextView charismaName;
|
||||
public final TextView charismaModifier;
|
||||
public final TextView charismaProficiency;
|
||||
public final TextView charismaAdvantage;
|
||||
public final TextView armorClass;
|
||||
public final TextView hitPoints;
|
||||
public final TextView challengeRating;
|
||||
public Monster monster;
|
||||
|
||||
public ViewHolder(@NonNull CardMonsterBinding binding) {
|
||||
super(binding.getRoot());
|
||||
name = binding.name;
|
||||
meta = binding.meta;
|
||||
action1Group = binding.action1.getRoot();
|
||||
action1Name = binding.action1.name;
|
||||
action1Description = binding.action1.description;
|
||||
action2Group = binding.action2.getRoot();
|
||||
action2Name = binding.action2.name;
|
||||
action2Description = binding.action2.description;
|
||||
action3Group = binding.action3.getRoot();
|
||||
action3Name = binding.action3.name;
|
||||
action3Description = binding.action3.description;
|
||||
strengthName = binding.strength.name;
|
||||
strengthModifier = binding.strength.modifier;
|
||||
strengthProficiency = binding.strength.proficiency;
|
||||
strengthAdvantage = binding.strength.advantage;
|
||||
dexterityName = binding.dexterity.name;
|
||||
dexterityModifier = binding.dexterity.modifier;
|
||||
dexterityProficiency = binding.dexterity.proficiency;
|
||||
dexterityAdvantage = binding.dexterity.advantage;
|
||||
constitutionName = binding.constitution.name;
|
||||
constitutionModifier = binding.constitution.modifier;
|
||||
constitutionProficiency = binding.constitution.proficiency;
|
||||
constitutionAdvantage = binding.constitution.advantage;
|
||||
intelligenceName = binding.intelligence.name;
|
||||
intelligenceModifier = binding.intelligence.modifier;
|
||||
intelligenceProficiency = binding.intelligence.proficiency;
|
||||
intelligenceAdvantage = binding.intelligence.advantage;
|
||||
wisdomName = binding.wisdom.name;
|
||||
wisdomModifier = binding.wisdom.modifier;
|
||||
wisdomProficiency = binding.wisdom.proficiency;
|
||||
wisdomAdvantage = binding.wisdom.advantage;
|
||||
charismaName = binding.charisma.name;
|
||||
charismaModifier = binding.charisma.modifier;
|
||||
charismaProficiency = binding.charisma.proficiency;
|
||||
charismaAdvantage = binding.charisma.advantage;
|
||||
armorClass = binding.armorClass.value;
|
||||
hitPoints = binding.hitPoints.value;
|
||||
challengeRating = binding.challengeRating;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Helpers {
|
||||
@NonNull
|
||||
public static String getModifierString(int value) {
|
||||
return String.format(Locale.getDefault(), "%+d", value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getAbilityScoreAbbreviation(@NonNull AbilityScore abilityScore) {
|
||||
switch (abilityScore) {
|
||||
case STRENGTH:
|
||||
return "S";
|
||||
case DEXTERITY:
|
||||
return "D";
|
||||
case CONSTITUTION:
|
||||
return "C";
|
||||
case INTELLIGENCE:
|
||||
return "I";
|
||||
case WISDOM:
|
||||
return "W";
|
||||
case CHARISMA:
|
||||
return "Ch";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for AbilityScore value %s", abilityScore));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getChallengeRatingAbbreviation(@NonNull ChallengeRating challengeRating) {
|
||||
switch (challengeRating) {
|
||||
case CUSTOM:
|
||||
return "*";
|
||||
case ZERO:
|
||||
return "0";
|
||||
case ONE_EIGHTH:
|
||||
return "1/8";
|
||||
case ONE_QUARTER:
|
||||
return "1/4";
|
||||
case ONE_HALF:
|
||||
return "1/2";
|
||||
case ONE:
|
||||
return "1";
|
||||
case TWO:
|
||||
return "2";
|
||||
case THREE:
|
||||
return "3";
|
||||
case FOUR:
|
||||
return "4";
|
||||
case FIVE:
|
||||
return "5";
|
||||
case SIX:
|
||||
return "6";
|
||||
case SEVEN:
|
||||
return "7";
|
||||
case EIGHT:
|
||||
return "8";
|
||||
case NINE:
|
||||
return "9";
|
||||
case TEN:
|
||||
return "10";
|
||||
case ELEVEN:
|
||||
return "11";
|
||||
case TWELVE:
|
||||
return "12";
|
||||
case THIRTEEN:
|
||||
return "13";
|
||||
case FOURTEEN:
|
||||
return "14";
|
||||
case FIFTEEN:
|
||||
return "15";
|
||||
case SIXTEEN:
|
||||
return "16";
|
||||
case SEVENTEEN:
|
||||
return "17";
|
||||
case EIGHTEEN:
|
||||
return "18";
|
||||
case NINETEEN:
|
||||
return "19";
|
||||
case TWENTY:
|
||||
return "20";
|
||||
case TWENTY_ONE:
|
||||
return "21";
|
||||
case TWENTY_TWO:
|
||||
return "22";
|
||||
case TWENTY_THREE:
|
||||
return "23";
|
||||
case TWENTY_FOUR:
|
||||
return "24";
|
||||
case TWENTY_FIVE:
|
||||
return "25";
|
||||
case TWENTY_SIX:
|
||||
return "26";
|
||||
case TWENTY_SEVEN:
|
||||
return "27";
|
||||
case TWENTY_EIGHT:
|
||||
return "28";
|
||||
case TWENTY_NINE:
|
||||
return "29";
|
||||
case THIRTY:
|
||||
return "30";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for ChallengeRating value %s", challengeRating));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getProficiencyAbbreviation(@NonNull ProficiencyType proficiency) {
|
||||
switch (proficiency) {
|
||||
case NONE:
|
||||
return "";
|
||||
case EXPERTISE:
|
||||
return "E";
|
||||
case PROFICIENT:
|
||||
return "P";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for ProficiencyType value %s", proficiency));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getAdvantageAbbreviation(@NonNull AdvantageType advantage) {
|
||||
switch (advantage) {
|
||||
case NONE:
|
||||
return "";
|
||||
case ADVANTAGE:
|
||||
return "A";
|
||||
case DISADVANTAGE:
|
||||
return "D";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for AdvantageType value %s", advantage));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
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.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.subscribers.DisposableSubscriber;
|
||||
|
||||
public class DashboardViewModel extends AndroidViewModel {
|
||||
private final AppDatabase mDB;
|
||||
private final MutableLiveData<List<Monster>> mMonsters;
|
||||
|
||||
public DashboardViewModel(Application application) {
|
||||
super(application);
|
||||
mDB = AppDatabase.getInstance(application);
|
||||
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() {
|
||||
return mMonsters;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class EditAbilityScoresFragment extends Fragment {
|
||||
private final String ABILITY_SCORE_FORMAT = "%d (%+d)";
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private int getModifier(int value) {
|
||||
return value / 2 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_ability_scores, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_ability_scores));
|
||||
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), value -> mHolder.strength.setValue(value));
|
||||
mHolder.strength.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setStrength(newValue));
|
||||
mHolder.strength.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), value -> mHolder.dexterity.setValue(value));
|
||||
mHolder.dexterity.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setDexterity(newValue));
|
||||
mHolder.dexterity.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), value -> mHolder.constitution.setValue(value));
|
||||
mHolder.constitution.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setConstitution(newValue));
|
||||
mHolder.constitution.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), value -> mHolder.intelligence.setValue(value));
|
||||
mHolder.intelligence.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setIntelligence(newValue));
|
||||
mHolder.intelligence.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), value -> mHolder.wisdom.setValue(value));
|
||||
mHolder.wisdom.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWisdom(newValue));
|
||||
mHolder.wisdom.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), value -> mHolder.charisma.setValue(value));
|
||||
mHolder.charisma.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setCharisma(newValue));
|
||||
mHolder.charisma.setOnFormatValueCallback(value -> String.format(ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final Stepper strength;
|
||||
final Stepper dexterity;
|
||||
final Stepper constitution;
|
||||
final Stepper intelligence;
|
||||
final Stepper wisdom;
|
||||
final Stepper charisma;
|
||||
|
||||
ViewHolder(View root) {
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditArmorFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_armor, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_armor));
|
||||
|
||||
mHolder.armorType.setAdapter(new ArrayAdapter<ArmorType>(requireContext(), R.layout.dropdown_list_item, ArmorType.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ArmorType item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ArmorType item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.armorType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ArmorType selectedItem = (ArmorType) parent.getItemAtPosition(position);
|
||||
mViewModel.setArmorType(selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mViewModel.setArmorType(ArmorType.NONE);
|
||||
}
|
||||
});
|
||||
mHolder.armorType.setSelection(ArrayHelper.indexOf(ArmorType.values(), mViewModel.getArmorType().getValue()));
|
||||
|
||||
mHolder.naturalArmorBonus.setValue(mViewModel.getNaturalArmorBonusUnboxed());
|
||||
mHolder.naturalArmorBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setNaturalArmorBonus(newValue));
|
||||
|
||||
mHolder.hasShield.setChecked(mViewModel.getHasShieldValueAsBoolean());
|
||||
mHolder.hasShield.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasShield(isChecked));
|
||||
|
||||
mHolder.shieldBonus.setValue(mViewModel.getShieldBonusUnboxed());
|
||||
mHolder.shieldBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setShieldBonus(newValue));
|
||||
|
||||
mHolder.customArmor.setText(mViewModel.getCustomArmor().getValue());
|
||||
mHolder.customArmor.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomArmor(s.toString()))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private final Spinner armorType;
|
||||
private final Stepper naturalArmorBonus;
|
||||
private final SwitchCompat hasShield;
|
||||
private final Stepper shieldBonus;
|
||||
private final EditText customArmor;
|
||||
|
||||
ViewHolder(View root) {
|
||||
armorType = root.findViewById(R.id.armorType);
|
||||
naturalArmorBonus = root.findViewById(R.id.naturalArmorBonus);
|
||||
hasShield = root.findViewById(R.id.hasShield);
|
||||
shieldBonus = root.findViewById(R.id.shieldBonus);
|
||||
customArmor = root.findViewById(R.id.customArmor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditBasicInfoFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_basic_info, container, false);
|
||||
setTitle(getString(R.string.title_edit_basic_info));
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.size.setText(mViewModel.getSize().getValue());
|
||||
mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSize(s.toString())));
|
||||
|
||||
mHolder.type.setText(mViewModel.getType().getValue());
|
||||
mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setType(s.toString())));
|
||||
|
||||
mHolder.subtype.setText(mViewModel.getSubtype().getValue());
|
||||
mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSubtype(s.toString())));
|
||||
|
||||
mHolder.alignment.setText(mViewModel.getAlignment().getValue());
|
||||
mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setAlignment(s.toString())));
|
||||
|
||||
mHolder.customHitPoints.setText(mViewModel.getCustomHitPoints().getValue());
|
||||
mHolder.customHitPoints.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomHitPoints(s.toString()))));
|
||||
|
||||
mHolder.hitDice.setValue(mViewModel.getHitDiceUnboxed());
|
||||
mHolder.hitDice.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setHitDice(newValue));
|
||||
|
||||
mHolder.hasCustomHitPoints.setChecked(mViewModel.getHasCustomHitPointsValueAsBoolean());
|
||||
mHolder.hasCustomHitPoints.setOnCheckedChangeListener((button, isChecked) -> mViewModel.setHasCustomHitPoints(isChecked));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private final EditText name;
|
||||
private final EditText size;
|
||||
private final EditText type;
|
||||
private final EditText subtype;
|
||||
private final EditText alignment;
|
||||
private final EditText customHitPoints;
|
||||
private final Stepper hitDice;
|
||||
private final SwitchMaterial hasCustomHitPoints;
|
||||
|
||||
ViewHolder(View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
size = root.findViewById(R.id.size);
|
||||
type = root.findViewById(R.id.type);
|
||||
subtype = root.findViewById(R.id.subtype);
|
||||
alignment = root.findViewById(R.id.alignment);
|
||||
customHitPoints = root.findViewById(R.id.customHitPoints);
|
||||
hitDice = root.findViewById(R.id.hitDice);
|
||||
hasCustomHitPoints = root.findViewById(R.id.hasCustomHitPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditChallengeRatingFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_challenge_rating, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_challenge_rating));
|
||||
|
||||
mHolder.challengeRating.setAdapter(new ArrayAdapter<ChallengeRating>(requireContext(), R.layout.dropdown_list_item, ChallengeRating.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ChallengeRating item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ChallengeRating item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.challengeRating.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ChallengeRating selectedItem = (ChallengeRating) parent.getItemAtPosition(position);
|
||||
mViewModel.setChallengeRating(selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mViewModel.setChallengeRating(ChallengeRating.CUSTOM);
|
||||
}
|
||||
});
|
||||
mHolder.challengeRating.setSelection(ArrayHelper.indexOf(ChallengeRating.values(), mViewModel.getChallengeRating().getValue()));
|
||||
|
||||
mHolder.customChallengeRatingDescription.setText(mViewModel.getCustomChallengeRatingDescription().getValue());
|
||||
mHolder.customChallengeRatingDescription.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomChallengeRatingDescription(s.toString()))));
|
||||
|
||||
mHolder.customProficiencyBonus.setText(mViewModel.getCustomProficiencyBonusValueAsString());
|
||||
mHolder.customProficiencyBonus.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomProficiencyBonus(s.toString()))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final Spinner challengeRating;
|
||||
final EditText customChallengeRatingDescription;
|
||||
final EditText customProficiencyBonus;
|
||||
|
||||
ViewHolder(View root) {
|
||||
challengeRating = root.findViewById(R.id.challengeRating);
|
||||
customChallengeRatingDescription = root.findViewById(R.id.customChallengeRatingDescription);
|
||||
customProficiencyBonus = root.findViewById(R.id.customProficiencyBonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditLanguageFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditLanguageViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private Language mOldLanguage;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditLanguageViewModel.class);
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
EditLanguageFragmentArgs args = EditLanguageFragmentArgs.fromBundle(arguments);
|
||||
mOldLanguage = new Language(args.getName(), args.getCanSpeak());
|
||||
mViewModel.copyFromLanguage(mOldLanguage);
|
||||
} else {
|
||||
Logger.logWTF("EditLanguageFragment needs arguments");
|
||||
mOldLanguage = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_language, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_language));
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.canSpeak.setChecked(mViewModel.getCanSpeakValue());
|
||||
mHolder.canSpeak.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setCanSpeak(isChecked));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceLanguage(mOldLanguage, mViewModel.getLanguage().getValue());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText name;
|
||||
SwitchCompat canSpeak;
|
||||
|
||||
ViewHolder(View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
canSpeak = root.findViewById(R.id.canSpeak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditLanguageViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<Boolean> mCanSpeak;
|
||||
private final ChangeTrackedLiveData<Language> mLanguage;
|
||||
|
||||
public EditLanguageViewModel() {
|
||||
super();
|
||||
mName = new ChangeTrackedLiveData<>("New Language", this::makeDirty);
|
||||
mCanSpeak = new ChangeTrackedLiveData<>(true, this::makeDirty);
|
||||
mLanguage = new ChangeTrackedLiveData<>(makeLanguage(), this::makeDirty);
|
||||
}
|
||||
|
||||
public void copyFromLanguage(Language language) {
|
||||
mName.resetValue(language.getName());
|
||||
mCanSpeak.resetValue(language.getSpeaks());
|
||||
makeClean();
|
||||
}
|
||||
|
||||
public LiveData<Language> getLanguage() {
|
||||
return mLanguage;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName.setValue(name);
|
||||
mLanguage.setValue(makeLanguage());
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getCanSpeak() {
|
||||
return mCanSpeak;
|
||||
}
|
||||
|
||||
public void setCanSpeak(boolean canSpeak) {
|
||||
mCanSpeak.setValue(canSpeak);
|
||||
mLanguage.setValue(makeLanguage());
|
||||
}
|
||||
|
||||
public boolean getCanSpeakValue(boolean defaultIfNull) {
|
||||
Boolean boxedValue = mCanSpeak.getValue();
|
||||
if (boxedValue == null) {
|
||||
return defaultIfNull;
|
||||
}
|
||||
return boxedValue;
|
||||
}
|
||||
|
||||
public boolean getCanSpeakValue() {
|
||||
return getCanSpeakValue(false);
|
||||
}
|
||||
|
||||
private Language makeLanguage() {
|
||||
Boolean boxedValue = mCanSpeak.getValue();
|
||||
boolean canSpeak = boxedValue != null && boxedValue;
|
||||
return new Language(mName.getValue(), canSpeak);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditLanguagesFragment extends MCFragment {
|
||||
// TODO: Make the swipe to delete not happen for the header
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private void navigateToEditLanguage(Language language) {
|
||||
NavDirections action = EditLanguagesFragmentDirections.actionEditLanguagesFragmentToEditLanguageFragment(language.getName(), language.getSpeaks());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_languages_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_languages));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddLanguageButton(mHolder.addLanguage);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> {
|
||||
EditLanguagesRecyclerViewAdapter adapter = new EditLanguagesRecyclerViewAdapter(
|
||||
mViewModel.getLanguagesArray(),
|
||||
language -> {
|
||||
if (language != null) {
|
||||
navigateToEditLanguage(language);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditSkill with a null skill");
|
||||
}
|
||||
},
|
||||
mViewModel.getTelepathyRangeUnboxed(),
|
||||
(value, previousValue) -> mViewModel.setTelepathyRange(value),
|
||||
mViewModel.getUnderstandsButDescription().getValue(),
|
||||
new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setUnderstandsButDescription(s.toString())));
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> {
|
||||
if (position > 0) {
|
||||
mViewModel.removeLanguage(position - 1);
|
||||
}
|
||||
}, null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddLanguageButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Language newLanguage = mViewModel.addNewLanguage();
|
||||
navigateToEditLanguage(newLanguage);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addLanguage;
|
||||
|
||||
ViewHolder(View root) {
|
||||
this.list = root.findViewById(R.id.list);
|
||||
this.addLanguage = root.findViewById(R.id.add_language);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListHeaderBinding;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private final List<Language> mValues;
|
||||
private final ItemCallback<Language> mOnClick;
|
||||
private final int mTelepathyRange;
|
||||
private final String mUnderstandsBut;
|
||||
private final Stepper.OnValueChangeListener mOnTelepathyRangeChanged;
|
||||
private final TextWatcher mOnUnderstandsButChanged;
|
||||
|
||||
private final int HEADER_VIEW_TYPE = 1;
|
||||
private final int ITEM_VIEW_TYPE = 2;
|
||||
private final String DISTANCE_IN_FEET_FORMAT = "%d ft.";
|
||||
|
||||
public EditLanguagesRecyclerViewAdapter(List<Language> items, ItemCallback<Language> onClick, int telepathyRange, Stepper.OnValueChangeListener telepathyRangeChangedListener, String understandsBut, TextWatcher understandsButChangedListener) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
mTelepathyRange = telepathyRange;
|
||||
mOnTelepathyRangeChanged = telepathyRangeChangedListener;
|
||||
mUnderstandsBut = understandsBut;
|
||||
mOnUnderstandsButChanged = understandsButChangedListener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == HEADER_VIEW_TYPE) {
|
||||
return new HeaderViewHolder(FragmentEditLanguagesListHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
return new ItemViewHolder(com.majinnaibu.monstercards.databinding.SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
|
||||
if (holder instanceof HeaderViewHolder) {
|
||||
HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder;
|
||||
headerViewHolder.telepathy.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), DISTANCE_IN_FEET_FORMAT, value));
|
||||
headerViewHolder.telepathy.setValue(mTelepathyRange);
|
||||
headerViewHolder.telepathy.setOnValueChangeListener(mOnTelepathyRangeChanged);
|
||||
headerViewHolder.understandsBut.setText(mUnderstandsBut);
|
||||
headerViewHolder.understandsBut.addTextChangedListener(mOnUnderstandsButChanged);
|
||||
} else if (holder instanceof ItemViewHolder) {
|
||||
ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
|
||||
itemViewHolder.mItem = mValues.get(position - 1);
|
||||
itemViewHolder.mContentView.setText(itemViewHolder.mItem.getName());
|
||||
itemViewHolder.itemView.setOnClickListener(view -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(itemViewHolder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return HEADER_VIEW_TYPE;
|
||||
}
|
||||
return ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
public final Stepper telepathy;
|
||||
public final EditText understandsBut;
|
||||
|
||||
public HeaderViewHolder(@NonNull FragmentEditLanguagesListHeaderBinding binding) {
|
||||
super(binding.getRoot());
|
||||
telepathy = binding.telepathy;
|
||||
understandsBut = binding.understandsBut;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Language mItem;
|
||||
|
||||
public ItemViewHolder(@NonNull com.majinnaibu.monstercards.databinding.SimpleListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentArgs;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class EditMonsterFragment extends MCFragment {
|
||||
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
|
||||
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
|
||||
View root = inflater.inflate(R.layout.fragment_edit_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setTitle(getString(R.string.title_edit_monster, getString(R.string.default_monster_name)));
|
||||
|
||||
// TODO: Show a loading spinner until we have the monster loaded.
|
||||
if (mViewModel.hasError() || !mViewModel.hasLoaded() || !Objects.equals(mViewModel.getMonsterId().getValue(), monsterId)) {
|
||||
repository.getMonster(monsterId).toObservable()
|
||||
.firstOrError()
|
||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||
mViewModel.setHasLoaded(true);
|
||||
mViewModel.setHasError(false);
|
||||
mViewModel.copyFromMonster(monster);
|
||||
setTitle(getString(R.string.title_edit_monster, monster.name));
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
// TODO: Show an error state.
|
||||
Logger.logError(e);
|
||||
mViewModel.setHasError(true);
|
||||
mViewModel.setErrorMessage(e.toString());
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mHolder.basicInfoButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditBasicInfoFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.armorButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditArmorFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.speedButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSpeedFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.abilityScoresButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditAbilityScoresFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.savingThrows.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSavingThrowsFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.challengeRating.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditChallengeRatingFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.skills.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSkillsFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.senses.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.SENSE);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.conditionImmunities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.CONDITION_IMMUNITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageImmunities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_IMMUNITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageResistances.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_RESISTANCE);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageVulnerabilities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_VULNERABILITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.languages.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditLanguagesFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.abilities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.ABILITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.actions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.lairActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.LAIR_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.legendaryActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.LEGENDARY_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.reactions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.REACTIONS);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.regionalActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.REGIONAL_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
View view = getView();
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(requireContext()).create();
|
||||
alertDialog.setTitle("Unsaved Changes");
|
||||
alertDialog.setMessage("Do you want to save your changes?");
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "Save", (dialog, id) -> {
|
||||
// Save the monster. Navigate up if the save is successful. Show a SnackBar if there was an error.
|
||||
getMonsterRepository().saveMonster(mViewModel.buildMonster())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
assert view != null;
|
||||
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Discard", (dialog, id) -> {
|
||||
// Navigate up ignoring unsaved changes.
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
});
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Cancel", (dialog, id) -> {
|
||||
// Do nothing.
|
||||
});
|
||||
alertDialog.show();
|
||||
} else {
|
||||
// No changes so we can safely leave.
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
TextView basicInfoButton;
|
||||
TextView armorButton;
|
||||
TextView speedButton;
|
||||
TextView abilityScoresButton;
|
||||
TextView savingThrows;
|
||||
TextView skills;
|
||||
TextView conditionImmunities;
|
||||
TextView damageImmunities;
|
||||
TextView damageResistances;
|
||||
TextView damageVulnerabilities;
|
||||
TextView senses;
|
||||
TextView languages;
|
||||
TextView challengeRating;
|
||||
TextView abilities;
|
||||
TextView actions;
|
||||
TextView reactions;
|
||||
TextView legendaryActions;
|
||||
TextView lairActions;
|
||||
TextView regionalActions;
|
||||
|
||||
ViewHolder(View root) {
|
||||
basicInfoButton = root.findViewById(R.id.basicInfo);
|
||||
armorButton = root.findViewById(R.id.armor);
|
||||
speedButton = root.findViewById(R.id.speed);
|
||||
abilityScoresButton = root.findViewById(R.id.abilityScores);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challengeRating = root.findViewById(R.id.challengeRating);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
regionalActions = root.findViewById(R.id.regionalActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class EditSavingThrowsFragment extends Fragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mViewHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_saving_throws, container, false);
|
||||
mViewHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_saving_throws));
|
||||
|
||||
mViewHolder.strengthProficiency.setValue(mViewModel.getStrengthProficiency().getValue());
|
||||
mViewHolder.strengthProficiency.setOnValueChangedListener(value -> mViewModel.setStrengthProficiency(value));
|
||||
mViewHolder.strengthAdvantage.setValue(mViewModel.getStrengthAdvantage().getValue());
|
||||
mViewHolder.strengthAdvantage.setOnValueChangedListener(value -> mViewModel.setStrengthAdvantage(value));
|
||||
|
||||
mViewHolder.dexterityProficiency.setValue(mViewModel.getDexterityProficiency().getValue());
|
||||
mViewHolder.dexterityProficiency.setOnValueChangedListener(value -> mViewModel.setDexterityProficiency(value));
|
||||
mViewHolder.dexterityAdvantage.setValue(mViewModel.getDexterityAdvantage().getValue());
|
||||
mViewHolder.dexterityAdvantage.setOnValueChangedListener(value -> mViewModel.setDexterityAdvantage(value));
|
||||
|
||||
mViewHolder.constitutionProficiency.setValue(mViewModel.getConstitutionProficiency().getValue());
|
||||
mViewHolder.constitutionProficiency.setOnValueChangedListener(value -> mViewModel.setConstitutionProficiency(value));
|
||||
mViewHolder.constitutionAdvantage.setValue(mViewModel.getConstitutionAdvantage().getValue());
|
||||
mViewHolder.constitutionAdvantage.setOnValueChangedListener(value -> mViewModel.setConstitutionAdvantage(value));
|
||||
|
||||
mViewHolder.intelligenceProficiency.setValue(mViewModel.getIntelligenceProficiency().getValue());
|
||||
mViewHolder.intelligenceProficiency.setOnValueChangedListener(value -> mViewModel.setIntelligenceProficiency(value));
|
||||
mViewHolder.intelligenceAdvantage.setValue(mViewModel.getIntelligenceAdvantage().getValue());
|
||||
mViewHolder.intelligenceAdvantage.setOnValueChangedListener(value -> mViewModel.setIntelligenceAdvantage(value));
|
||||
|
||||
mViewHolder.wisdomProficiency.setValue(mViewModel.getWisdomProficiency().getValue());
|
||||
mViewHolder.wisdomProficiency.setOnValueChangedListener(value -> mViewModel.setWisdomProficiency(value));
|
||||
mViewHolder.wisdomAdvantage.setValue(mViewModel.getWisdomAdvantage().getValue());
|
||||
mViewHolder.wisdomAdvantage.setOnValueChangedListener(value -> mViewModel.setWisdomAdvantage(value));
|
||||
|
||||
mViewHolder.charismaProficiency.setValue(mViewModel.getCharismaProficiency().getValue());
|
||||
mViewHolder.charismaProficiency.setOnValueChangedListener(value -> mViewModel.setCharismaProficiency(value));
|
||||
mViewHolder.charismaAdvantage.setValue(mViewModel.getCharismaAdvantage().getValue());
|
||||
mViewHolder.charismaAdvantage.setOnValueChangedListener(value -> mViewModel.setCharismaAdvantage(value));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
AdvantagePicker strengthAdvantage;
|
||||
ProficiencyPicker strengthProficiency;
|
||||
AdvantagePicker dexterityAdvantage;
|
||||
ProficiencyPicker dexterityProficiency;
|
||||
AdvantagePicker constitutionAdvantage;
|
||||
ProficiencyPicker constitutionProficiency;
|
||||
AdvantagePicker intelligenceAdvantage;
|
||||
ProficiencyPicker intelligenceProficiency;
|
||||
AdvantagePicker wisdomAdvantage;
|
||||
ProficiencyPicker wisdomProficiency;
|
||||
AdvantagePicker charismaAdvantage;
|
||||
ProficiencyPicker charismaProficiency;
|
||||
|
||||
ViewHolder(View root) {
|
||||
strengthAdvantage = root.findViewById(R.id.strengthAdvantage);
|
||||
strengthProficiency = root.findViewById(R.id.strengthProficiency);
|
||||
dexterityAdvantage = root.findViewById(R.id.dexterityAdvantage);
|
||||
dexterityProficiency = root.findViewById(R.id.dexterityProficiency);
|
||||
constitutionAdvantage = root.findViewById(R.id.constitutionAdvantage);
|
||||
constitutionProficiency = root.findViewById(R.id.constitutionProficiency);
|
||||
intelligenceAdvantage = root.findViewById(R.id.intelligenceAdvantage);
|
||||
intelligenceProficiency = root.findViewById(R.id.intelligenceProficiency);
|
||||
wisdomAdvantage = root.findViewById(R.id.wisdomAdvantage);
|
||||
wisdomProficiency = root.findViewById(R.id.wisdomProficiency);
|
||||
charismaAdvantage = root.findViewById(R.id.charismaAdvantage);
|
||||
charismaProficiency = root.findViewById(R.id.charismaProficiency);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.components.AbilityScorePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditSkillFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditSkillViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private Skill mOldSkill;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditSkillViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditSkillFragmentArgs args = EditSkillFragmentArgs.fromBundle(getArguments());
|
||||
mOldSkill = new Skill(args.getName(), args.getAbilityScore(), args.getAdvantage(), args.getProficiency());
|
||||
mViewModel.copyFromSkill(mOldSkill);
|
||||
} else {
|
||||
Logger.logWTF("EditSkillFragment needs arguments.");
|
||||
mOldSkill = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_skill, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_skill));
|
||||
|
||||
mHolder.abilityScore.setValue(mViewModel.getAbilityScore().getValue());
|
||||
mHolder.abilityScore.setOnValueChangedListener(value -> mViewModel.setAbilityScore(value));
|
||||
|
||||
mHolder.advantage.setValue(mViewModel.getAdvantage().getValue());
|
||||
mHolder.advantage.setOnValueChangedListener(value -> mViewModel.setAdvantage(value));
|
||||
|
||||
mHolder.proficiency.setValue(mViewModel.getProficiency().getValue());
|
||||
mHolder.proficiency.setOnValueChangedListener(value -> mViewModel.setProficiency(value));
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceSkill(mViewModel.getSkill().getValue(), mOldSkill);
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
AbilityScorePicker abilityScore;
|
||||
AdvantagePicker advantage;
|
||||
ProficiencyPicker proficiency;
|
||||
EditText name;
|
||||
|
||||
ViewHolder(View root) {
|
||||
abilityScore = root.findViewById(R.id.abilityScore);
|
||||
advantage = root.findViewById(R.id.advantage);
|
||||
proficiency = root.findViewById(R.id.proficiency);
|
||||
name = root.findViewById(R.id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditSkillViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<AbilityScore> mAbilityScore;
|
||||
private final ChangeTrackedLiveData<AdvantageType> mAdvantageType;
|
||||
private final ChangeTrackedLiveData<ProficiencyType> mProficiencyType;
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<Skill> mSkill;
|
||||
|
||||
public EditSkillViewModel() {
|
||||
super();
|
||||
mAbilityScore = new ChangeTrackedLiveData<>(AbilityScore.STRENGTH, this::makeDirty);
|
||||
mAdvantageType = new ChangeTrackedLiveData<>(AdvantageType.NONE, this::makeDirty);
|
||||
mProficiencyType = new ChangeTrackedLiveData<>(ProficiencyType.NONE, this::makeDirty);
|
||||
mName = new ChangeTrackedLiveData<>("Unknown Skill", this::makeDirty);
|
||||
mSkill = new ChangeTrackedLiveData<>(makeSkill(), this::makeDirty);
|
||||
}
|
||||
|
||||
public void copyFromSkill(Skill skill) {
|
||||
mAbilityScore.resetValue(skill.abilityScore);
|
||||
mAdvantageType.resetValue(skill.advantageType);
|
||||
mProficiencyType.resetValue(skill.proficiencyType);
|
||||
mName.resetValue(skill.name);
|
||||
}
|
||||
|
||||
public LiveData<Skill> getSkill() {
|
||||
return mSkill;
|
||||
}
|
||||
|
||||
public LiveData<AbilityScore> getAbilityScore() {
|
||||
return mAbilityScore;
|
||||
}
|
||||
|
||||
public void setAbilityScore(AbilityScore value) {
|
||||
mAbilityScore.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<AdvantageType> getAdvantage() {
|
||||
return mAdvantageType;
|
||||
}
|
||||
|
||||
public void setAdvantage(AdvantageType value) {
|
||||
mAdvantageType.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<ProficiencyType> getProficiency() {
|
||||
return mProficiencyType;
|
||||
}
|
||||
|
||||
public void setProficiency(ProficiencyType value) {
|
||||
mProficiencyType.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String value) {
|
||||
mName.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
private Skill makeSkill() {
|
||||
return new Skill(mName.getValue(), mAbilityScore.getValue(), mAdvantageType.getValue(), mProficiencyType.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Items.
|
||||
*/
|
||||
public class EditSkillsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private void navigateToEditSkill(Skill skill) {
|
||||
NavDirections action = EditSkillsFragmentDirections.actionEditSkillsFragmentToEditSkillFragment(skill.name, skill.abilityScore, skill.proficiencyType, skill.advantageType);
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Navigation.findNavController(view).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_skills_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_skills));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddSkillButton(mHolder.addSkill);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> {
|
||||
EditSkillsRecyclerViewAdapter adapter = new EditSkillsRecyclerViewAdapter(mViewModel.getSkillsArray(), skill -> {
|
||||
if (skill != null) {
|
||||
navigateToEditSkill(skill);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditSkill with a null skill");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeSkill(position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddSkillButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Skill newSkill = mViewModel.addNewSkill();
|
||||
navigateToEditSkill(newSkill);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addSkill;
|
||||
|
||||
ViewHolder(View root) {
|
||||
this.list = root.findViewById(R.id.list);
|
||||
this.addSkill = root.findViewById(R.id.add_skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link RecyclerView.Adapter} that can display a {@link Skill}.
|
||||
*/
|
||||
public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkillsRecyclerViewAdapter.ViewHolder> {
|
||||
private final List<Skill> mValues;
|
||||
private final ItemCallback<Skill> mOnClick;
|
||||
|
||||
public EditSkillsRecyclerViewAdapter(List<Skill> items, ItemCallback<Skill> onClick) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(com.majinnaibu.monstercards.databinding.SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mContentView.setText(mValues.get(position).name);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Skill mItem;
|
||||
|
||||
public ViewHolder(@NonNull com.majinnaibu.monstercards.databinding.SimpleListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditSpeedFragment extends Fragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_speed, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getString(R.string.title_edit_speed));
|
||||
|
||||
mHolder.baseSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWalkSpeed(newValue));
|
||||
mHolder.baseSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getWalkSpeed().observe(getViewLifecycleOwner(), value -> mHolder.baseSpeed.setValue(value));
|
||||
|
||||
mHolder.burrowSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setBurrowSpeed(newValue));
|
||||
mHolder.burrowSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getBurrowSpeed().observe(getViewLifecycleOwner(), value -> mHolder.burrowSpeed.setValue(value));
|
||||
|
||||
mHolder.climbSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setClimbSpeed(newValue));
|
||||
mHolder.climbSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getClimbSpeed().observe(getViewLifecycleOwner(), value -> mHolder.climbSpeed.setValue(value));
|
||||
|
||||
mHolder.flySpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setFlySpeed(newValue));
|
||||
mHolder.flySpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getFlySpeed().observe(getViewLifecycleOwner(), value -> mHolder.flySpeed.setValue(value));
|
||||
|
||||
mHolder.swimSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setSwimSpeed(newValue));
|
||||
mHolder.swimSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getSwimSpeed().observe(getViewLifecycleOwner(), value -> mHolder.swimSpeed.setValue(value));
|
||||
|
||||
mViewModel.getCanHover().observe(getViewLifecycleOwner(), value -> mHolder.canHover.setChecked(value));
|
||||
mHolder.canHover.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setCanHover(isChecked));
|
||||
|
||||
mViewModel.getHasCustomSpeed().observe(getViewLifecycleOwner(), value -> mHolder.hasCustomSpeed.setChecked(value));
|
||||
mHolder.hasCustomSpeed.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasCustomSpeed(isChecked));
|
||||
|
||||
//mViewModel.getCustomSpeed().observe(getViewLifecycleOwner(), value -> mHolder.customSpeed.setText(value));
|
||||
mHolder.customSpeed.setText(mViewModel.getCustomSpeed().getValue());
|
||||
mHolder.customSpeed.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomSpeed(s.toString())));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
final Stepper baseSpeed;
|
||||
final Stepper burrowSpeed;
|
||||
final Stepper climbSpeed;
|
||||
final Stepper flySpeed;
|
||||
final Stepper swimSpeed;
|
||||
final SwitchCompat canHover;
|
||||
final SwitchCompat hasCustomSpeed;
|
||||
final EditText customSpeed;
|
||||
|
||||
ViewHolder(View root) {
|
||||
baseSpeed = root.findViewById(R.id.baseSpeed);
|
||||
burrowSpeed = root.findViewById(R.id.burrowSpeed);
|
||||
climbSpeed = root.findViewById(R.id.climbSpeed);
|
||||
flySpeed = root.findViewById(R.id.flySpeed);
|
||||
swimSpeed = root.findViewById(R.id.swimSpeed);
|
||||
canHover = root.findViewById(R.id.canHover);
|
||||
hasCustomSpeed = root.findViewById(R.id.hasCustomSpeed);
|
||||
customSpeed = root.findViewById(R.id.customSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditStringFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditStringViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private String mOldValue;
|
||||
private StringType mStringType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditStringViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditStringFragmentArgs args = EditStringFragmentArgs.fromBundle(getArguments());
|
||||
mOldValue = args.getValue();
|
||||
mViewModel.setValue(mOldValue);
|
||||
mStringType = args.getStringType();
|
||||
} else {
|
||||
Logger.logWTF("EditStringFragment needs arguments");
|
||||
mOldValue = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@org.jetbrains.annotations.Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_string, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForStringType(mStringType));
|
||||
|
||||
mHolder.description.setText(mViewModel.getValueAsString());
|
||||
mHolder.description.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setValue(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceString(mStringType, mOldValue, mViewModel.getValueAsString());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private String getTitleForStringType(StringType type) {
|
||||
switch (type) {
|
||||
case CONDITION_IMMUNITY:
|
||||
return getString(R.string.title_edit_condition_immunity);
|
||||
case DAMAGE_IMMUNITY:
|
||||
return getString(R.string.title_edit_damage_immunity);
|
||||
case DAMAGE_RESISTANCE:
|
||||
return getString(R.string.title_edit_damage_resistance);
|
||||
case DAMAGE_VULNERABILITY:
|
||||
return getString(R.string.title_edit_damage_vulnerability);
|
||||
case SENSE:
|
||||
return getString(R.string.title_edit_sense);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.description.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText description;
|
||||
|
||||
ViewHolder(View root) {
|
||||
description = root.findViewById(R.id.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditStringViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mValue;
|
||||
|
||||
public EditStringViewModel() {
|
||||
super();
|
||||
mValue = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
}
|
||||
|
||||
public LiveData<String> getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
mValue.setValue(value);
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
return mValue.getValue();
|
||||
}
|
||||
|
||||
public void resetValue(String value) {
|
||||
makeClean();
|
||||
mValue.resetValue(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
<<<<<<<< HEAD:Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditConditionImmunitiesFragment.java
|
||||
import androidx.fragment.app.Fragment;
|
||||
========
|
||||
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.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
<<<<<<<< HEAD:Android/app/src/main/java/com/majinnaibu/monstercards/ui/editmonster/EditConditionImmunitiesFragment.java
|
||||
========
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
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.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;
|
||||
|
||||
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 ViewHolder mHolder;
|
||||
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
|
||||
public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
EditStringsFragmentArgs args = EditStringsFragmentArgs.fromBundle(arguments);
|
||||
mStringType = args.getStringType();
|
||||
} else {
|
||||
Logger.logWTF("EditStringsFragment needs arguments");
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
>>>>>>>> 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
|
||||
@org.jetbrains.annotations.Nullable
|
||||
@Override
|
||||
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);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_strings_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForStringType(mStringType));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddButton(mHolder.addItem);
|
||||
return root;
|
||||
}
|
||||
|
||||
private String getTitleForStringType(StringType type) {
|
||||
switch (type) {
|
||||
case CONDITION_IMMUNITY:
|
||||
return getString(R.string.title_edit_condition_immunities);
|
||||
case DAMAGE_IMMUNITY:
|
||||
return getString(R.string.title_edit_damage_immunities);
|
||||
case DAMAGE_RESISTANCE:
|
||||
return getString(R.string.title_edit_damage_resistances);
|
||||
case DAMAGE_VULNERABILITY:
|
||||
return getString(R.string.title_edit_damage_vulnerabilities);
|
||||
case SENSE:
|
||||
return getString(R.string.title_edit_senses);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<String>> stringsData = mViewModel.getStrings(mStringType);
|
||||
if (stringsData != null) {
|
||||
stringsData.observe(getViewLifecycleOwner(), strings -> {
|
||||
EditStringsRecyclerViewAdapter adapter = new EditStringsRecyclerViewAdapter(strings, value -> {
|
||||
if (value != null) {
|
||||
navigateToEditString(value);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditStringFragment with a null trait");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
}
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeString(mStringType, position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
String newValue = mViewModel.addNewString(mStringType);
|
||||
if (newValue != null) {
|
||||
navigateToEditString(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditString(String value) {
|
||||
NavDirections action = EditStringsFragmentDirections.actionEditStringsFragmentToEditStringFragment(mStringType, value);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addItem;
|
||||
|
||||
ViewHolder(View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
addItem = root.findViewById(R.id.add_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStringsRecyclerViewAdapter.ViewHolder> {
|
||||
private final List<String> mValues;
|
||||
private final ItemCallback<String> mOnClick;
|
||||
|
||||
public EditStringsRecyclerViewAdapter(List<String> items, ItemCallback<String> onClick) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mContentView.setText(mValues.get(position));
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public String mItem;
|
||||
|
||||
public ViewHolder(@NonNull SimpleListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditTraitFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditTraitViewModel mViewModel;
|
||||
private EditTraitFragment.ViewHolder mHolder;
|
||||
private Trait mOldValue;
|
||||
private TraitType mTraitType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditTraitViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditTraitFragmentArgs args = EditTraitFragmentArgs.fromBundle(getArguments());
|
||||
mOldValue = new Trait(args.getName(), args.getDescription());
|
||||
mViewModel.copyFromTrait(mOldValue);
|
||||
mTraitType = args.getTraitType();
|
||||
} else {
|
||||
Logger.logWTF("EditTraitFragment needs arguments");
|
||||
mOldValue = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@org.jetbrains.annotations.Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_trait, container, false);
|
||||
mHolder = new EditTraitFragment.ViewHolder(root);
|
||||
setTitle(getTitleForTraitType(mTraitType));
|
||||
|
||||
mHolder.name.setText(mViewModel.getNameAsString());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.description.setText(mViewModel.getDescriptionAsString());
|
||||
mHolder.description.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setDescription(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceTrait(mTraitType, mOldValue, mViewModel.getAbilityValue());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private String getTitleForTraitType(TraitType type) {
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
return getString(R.string.title_edit_ability);
|
||||
case ACTION:
|
||||
return getString(R.string.title_edit_action);
|
||||
case LAIR_ACTION:
|
||||
return getString(R.string.title_edit_lair_action);
|
||||
case LEGENDARY_ACTION:
|
||||
return getString(R.string.title_edit_legendary_action);
|
||||
case REACTIONS:
|
||||
return getString(R.string.title_edit_reaction);
|
||||
case REGIONAL_ACTION:
|
||||
return getString(R.string.title_edit_regional_action);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText description;
|
||||
EditText name;
|
||||
|
||||
ViewHolder(View root) {
|
||||
description = root.findViewById(R.id.description);
|
||||
name = root.findViewById(R.id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditTraitViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<String> mDescription;
|
||||
private final MutableLiveData<Trait> mAbility;
|
||||
|
||||
public EditTraitViewModel() {
|
||||
super();
|
||||
mName = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
mDescription = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
mAbility = new MutableLiveData<>(makeAbility());
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName.setValue(name);
|
||||
mAbility.setValue(makeAbility());
|
||||
}
|
||||
|
||||
public String getNameAsString() {
|
||||
return mName.getValue();
|
||||
}
|
||||
|
||||
public LiveData<String> getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
mDescription.setValue(description);
|
||||
mAbility.setValue(makeAbility());
|
||||
}
|
||||
|
||||
public String getDescriptionAsString() {
|
||||
return mDescription.getValue();
|
||||
}
|
||||
|
||||
public Trait getAbilityValue() {
|
||||
return mAbility.getValue();
|
||||
}
|
||||
|
||||
public void copyFromTrait(Trait trait) {
|
||||
makeClean();
|
||||
mName.resetValue(trait.name);
|
||||
mDescription.resetValue(trait.description);
|
||||
}
|
||||
|
||||
private Trait makeAbility() {
|
||||
return new Trait(mName.getValue(), mDescription.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditTraitsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private TraitType mTraitType;
|
||||
private EditTraitsRecyclerViewAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
if (getArguments() != null) {
|
||||
EditTraitsFragmentArgs args = EditTraitsFragmentArgs.fromBundle(getArguments());
|
||||
mTraitType = args.getTraitType();
|
||||
} else {
|
||||
Logger.logWTF("EditTraitFragment needs arguments");
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_traits_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForTraitType(mTraitType));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddButton(mHolder.addTrait);
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForTraitType(TraitType type) {
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
return getString(R.string.title_editAbilities);
|
||||
case ACTION:
|
||||
return getString(R.string.title_editActions);
|
||||
case LAIR_ACTION:
|
||||
return getString(R.string.title_editLairActions);
|
||||
case LEGENDARY_ACTION:
|
||||
return getString(R.string.title_editLegendaryActions);
|
||||
case REACTIONS:
|
||||
return getString(R.string.title_editReactions);
|
||||
case REGIONAL_ACTION:
|
||||
return getString(R.string.title_editRegionalActions);
|
||||
default:
|
||||
return getString(R.string.title_editTraits);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<Trait>> traitData = mViewModel.getTraits(mTraitType);
|
||||
mAdapter = new EditTraitsRecyclerViewAdapter(trait -> {
|
||||
if (trait != null) {
|
||||
navigateToEditTrait(trait);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditTraitFragment with a null trait");
|
||||
}
|
||||
});
|
||||
if (traitData != null) {
|
||||
traitData.observe(getViewLifecycleOwner(), traits -> mAdapter.submitList(traits));
|
||||
}
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeTrait(mTraitType, position), (from, to) -> mViewModel.moveTrait(mTraitType, from, to)));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Trait newTrait = mViewModel.addNewTrait(mTraitType);
|
||||
if (newTrait != null) {
|
||||
navigateToEditTrait(newTrait);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditTrait(@NonNull Trait trait) {
|
||||
NavDirections action = EditTraitsFragmentDirections.actionEditTraitListFragmentToEditTraitFragment(trait.description, trait.name, mTraitType);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addTrait;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
addTrait = root.findViewById(R.id.add_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
|
||||
public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraitsRecyclerViewAdapter.ViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Trait> DIFF_CALLBACK = new DiffUtil.ItemCallback<Trait>() {
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Trait oldItem, @NonNull Trait newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Trait oldItem, @NonNull Trait newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback<Trait> mOnClick;
|
||||
|
||||
protected EditTraitsRecyclerViewAdapter(ItemCallback<Trait> onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = getItem(position);
|
||||
holder.mContentView.setText(holder.mItem.name);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Trait mItem;
|
||||
|
||||
public ViewHolder(@NonNull SimpleListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.majinnaibu.monstercards.ui.library;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.databinding.FragmentLibraryBinding;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class LibraryFragment extends MCFragment {
|
||||
private LibraryViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private LibraryRecyclerViewAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(LibraryViewModel.class);
|
||||
FragmentLibraryBinding binding = FragmentLibraryBinding.inflate(inflater, container, false);
|
||||
mHolder = new ViewHolder(binding);
|
||||
// TODO: set the title with setTitle(...)
|
||||
setupAddMonsterButton(mHolder.addButton);
|
||||
setupMonsterList(mHolder.list);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupMonsterList(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
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());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(
|
||||
requireContext(),
|
||||
(position, direction) -> mViewModel.removeMonster(position),
|
||||
null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddMonsterButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Monster monster = new Monster();
|
||||
monster.name = getString(R.string.default_monster_name);
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
repository.addMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Snackbar.make(
|
||||
view,
|
||||
getString(R.string.snackbar_monster_created, monster.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", (_view) -> navigateToMonsterDetail(monster))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToMonsterDetail(Monster monster) {
|
||||
if (monster != null) {
|
||||
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monster.id.toString());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.majinnaibu.monstercards.ui.library;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.SimpleListItemViewHolder;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
|
||||
public class LibraryRecyclerViewAdapter extends ListAdapter<Monster, SimpleListItemViewHolder<Monster>> {
|
||||
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return Monster.areItemsTheSame(oldItem, newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return Monster.areContentsTheSame(oldItem, newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback<Monster> mOnClick;
|
||||
|
||||
public LibraryRecyclerViewAdapter(ItemCallback<Monster> onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public SimpleListItemViewHolder<Monster> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
SimpleListItemBinding binding = SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new SimpleListItemViewHolder<>(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final @NonNull SimpleListItemViewHolder<Monster> holder, int position) {
|
||||
Monster monster = getItem(position);
|
||||
holder.item = monster;
|
||||
holder.contentView.setText(monster.name);
|
||||
holder.itemView.setTag(monster);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(holder.item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
|
||||
|
||||
public class MonsterDetailFragment extends MCFragment {
|
||||
|
||||
private MonsterDetailViewModel monsterDetailViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
monsterDetailViewModel = new ViewModelProvider(this).get(MonsterDetailViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_monster, container, false);
|
||||
|
||||
repository.getMonster(monsterId).toObservable()
|
||||
.firstOrError()
|
||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||
monsterDetailViewModel.setMonster(monster);
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
|
||||
final TextView monsterName = root.findViewById(R.id.name);
|
||||
monsterDetailViewModel.getName().observe(getViewLifecycleOwner(), monsterName::setText);
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
final TextView monsterSkills = root.findViewById(R.id.skills);
|
||||
monsterDetailViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> {
|
||||
if (StringHelper.isNullOrEmpty(skills)) {
|
||||
monsterSkills.setVisibility(View.GONE);
|
||||
} else {
|
||||
monsterSkills.setVisibility(View.VISIBLE);
|
||||
}
|
||||
monsterSkills.setText(Html.fromHtml("<b>Skills</b> " + skills));
|
||||
});
|
||||
|
||||
final TextView monsterDamageVulnerabilities = root.findViewById(R.id.damage_vulnerabilities);
|
||||
monsterDetailViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageType -> {
|
||||
if (StringHelper.isNullOrEmpty(damageType)) {
|
||||
monsterDamageVulnerabilities.setVisibility(View.GONE);
|
||||
} else {
|
||||
monsterDamageVulnerabilities.setVisibility(View.VISIBLE);
|
||||
}
|
||||
monsterDamageVulnerabilities.setText(Html.fromHtml("<b>Damage Vulnerabilities</b> " + damageType));
|
||||
});
|
||||
|
||||
final TextView monsterDamageResistances = root.findViewById(R.id.damage_resistances);
|
||||
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));
|
||||
});
|
||||
|
||||
final TextView monsterDamageImmunities = root.findViewById(R.id.damage_immunities);
|
||||
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();
|
||||
DisplayMetrics displayMetrics = null;
|
||||
if (context != null) {
|
||||
Resources resources = context.getResources();
|
||||
if (resources != null) {
|
||||
displayMetrics = resources.getDisplayMetrics();
|
||||
}
|
||||
}
|
||||
monsterAbilities.removeAllViews();
|
||||
if (abilities != null) {
|
||||
for (String ability : abilities) {
|
||||
TextView tvAbility = new TextView(context);
|
||||
// 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
|
||||
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)));
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
|
||||
tvAction.setLayoutParams(layoutParams);
|
||||
monsterActions.addView(tvAction);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: add lair actions, legendary actions, reactions, and regional actions
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.monster_detail_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_action_edit_monster) {
|
||||
UUID monsterId = monsterDetailViewModel.getId().getValue();
|
||||
if (monsterId != null) {
|
||||
NavDirections action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Navigation.findNavController(view).navigate(action);
|
||||
} else {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MonsterDetailViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<List<String>> mAbilities;
|
||||
private final MutableLiveData<List<String>> mActions;
|
||||
private final MutableLiveData<String> mArmorClass;
|
||||
private final MutableLiveData<String> mChallenge;
|
||||
private final MutableLiveData<String> mCharisma;
|
||||
private final MutableLiveData<String> mConditionImmunities;
|
||||
private final MutableLiveData<String> mConstitution;
|
||||
private final MutableLiveData<String> mDamageResistances;
|
||||
private final MutableLiveData<String> mDamageImmunities;
|
||||
private final MutableLiveData<String> mDamageVulnerabilities;
|
||||
private final MutableLiveData<String> mDexterity;
|
||||
private final MutableLiveData<String> mHitPoints;
|
||||
private final MutableLiveData<String> mIntelligence;
|
||||
private final MutableLiveData<List<String>> mLairActions;
|
||||
private final MutableLiveData<String> mLanguages;
|
||||
private final MutableLiveData<List<String>> mLegendaryActions;
|
||||
private final MutableLiveData<String> mMeta;
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<List<String>> mReactions;
|
||||
private final MutableLiveData<List<String>> mRegionalEffects;
|
||||
private final MutableLiveData<String> mSavingThrows;
|
||||
private final MutableLiveData<String> mSenses;
|
||||
private final MutableLiveData<String> mSkills;
|
||||
private final MutableLiveData<String> mSpeed;
|
||||
private final MutableLiveData<String> mStrength;
|
||||
private final MutableLiveData<String> mWisdom;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
private Monster mMonster;
|
||||
|
||||
public MonsterDetailViewModel() {
|
||||
mMonster = null;
|
||||
mAbilities = new MutableLiveData<>(new ArrayList<>());
|
||||
mActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mArmorClass = new MutableLiveData<>("");
|
||||
mChallenge = new MutableLiveData<>("");
|
||||
mCharisma = new MutableLiveData<>("");
|
||||
mConditionImmunities = new MutableLiveData<>("");
|
||||
mConstitution = new MutableLiveData<>("");
|
||||
mDamageImmunities = new MutableLiveData<>("");
|
||||
mDamageResistances = new MutableLiveData<>("");
|
||||
mDamageVulnerabilities = new MutableLiveData<>("");
|
||||
mDexterity = new MutableLiveData<>("");
|
||||
mHitPoints = new MutableLiveData<>("");
|
||||
mIntelligence = new MutableLiveData<>("");
|
||||
mLairActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mLanguages = new MutableLiveData<>("");
|
||||
mLegendaryActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mMeta = new MutableLiveData<>("");
|
||||
mName = new MutableLiveData<>("");
|
||||
mReactions = new MutableLiveData<>(new ArrayList<>());
|
||||
mRegionalEffects = new MutableLiveData<>(new ArrayList<>());
|
||||
mSavingThrows = new MutableLiveData<>("");
|
||||
mSenses = new MutableLiveData<>("");
|
||||
mSkills = new MutableLiveData<>("");
|
||||
mSpeed = new MutableLiveData<>("");
|
||||
mStrength = new MutableLiveData<>("");
|
||||
mWisdom = new MutableLiveData<>("");
|
||||
mMonsterId = new MutableLiveData<>(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getAbilities() {
|
||||
return mAbilities;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getActions() {
|
||||
return mActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getReactions() {
|
||||
return mReactions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLegendaryActions() {
|
||||
return mLegendaryActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLairActions() {
|
||||
return mLairActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getRegionalEffects() {
|
||||
return mRegionalEffects;
|
||||
}
|
||||
|
||||
public LiveData<String> getArmorClass() {
|
||||
return mArmorClass;
|
||||
}
|
||||
|
||||
public LiveData<String> getChallenge() {
|
||||
return mChallenge;
|
||||
}
|
||||
|
||||
public LiveData<String> getCharisma() {
|
||||
return mCharisma;
|
||||
}
|
||||
|
||||
public LiveData<String> getConditionImmunities() {
|
||||
return mConditionImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getConstitution() {
|
||||
return mConstitution;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageResistances() {
|
||||
return mDamageResistances;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageImmunities() {
|
||||
return mDamageImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageVulnerabilities() {
|
||||
return mDamageVulnerabilities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDexterity() {
|
||||
return mDexterity;
|
||||
}
|
||||
|
||||
public LiveData<String> getHitPoints() {
|
||||
return mHitPoints;
|
||||
}
|
||||
|
||||
public LiveData<String> getIntelligence() {
|
||||
return mIntelligence;
|
||||
}
|
||||
|
||||
public LiveData<String> getLanguages() {
|
||||
return mLanguages;
|
||||
}
|
||||
|
||||
public LiveData<String> getMeta() {
|
||||
return mMeta;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public LiveData<String> getSavingThrows() {
|
||||
return mSavingThrows;
|
||||
}
|
||||
|
||||
public LiveData<String> getSenses() {
|
||||
return mSenses;
|
||||
}
|
||||
|
||||
public LiveData<String> getSkills() {
|
||||
return mSkills;
|
||||
}
|
||||
|
||||
public LiveData<String> getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
public LiveData<String> getStrength() {
|
||||
return mStrength;
|
||||
}
|
||||
|
||||
public LiveData<String> getWisdom() {
|
||||
return mWisdom;
|
||||
}
|
||||
|
||||
public LiveData<UUID> getId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
|
||||
public void setMonster(Monster monster) {
|
||||
mMonster = monster;
|
||||
mAbilities.setValue(mMonster.getAbilityDescriptions());
|
||||
mActions.setValue(mMonster.getActionDescriptions());
|
||||
mArmorClass.setValue(mMonster.getArmorClass());
|
||||
mChallenge.setValue(mMonster.getChallengeRatingDescription());
|
||||
mCharisma.setValue(monster.getCharismaDescription());
|
||||
mConditionImmunities.setValue(mMonster.getConditionImmunitiesDescription());
|
||||
mConstitution.setValue(monster.getConstitutionDescription());
|
||||
mDamageImmunities.setValue(mMonster.getDamageImmunitiesDescription());
|
||||
mDamageResistances.setValue(mMonster.getDamageResistancesDescription());
|
||||
mDamageVulnerabilities.setValue(mMonster.getDamageVulnerabilitiesDescription());
|
||||
mDexterity.setValue(monster.getDexterityDescription());
|
||||
mHitPoints.setValue(mMonster.getHitPoints());
|
||||
mIntelligence.setValue(monster.getIntelligenceDescription());
|
||||
mLairActions.setValue(mMonster.getLairActionDescriptions());
|
||||
mLanguages.setValue(mMonster.getLanguagesDescription());
|
||||
mLegendaryActions.setValue(mMonster.getLegendaryActionDescriptions());
|
||||
mMeta.setValue(mMonster.getMeta());
|
||||
mMonsterId.setValue(mMonster.id);
|
||||
mName.setValue(mMonster.name);
|
||||
mReactions.setValue(monster.getReactionDescriptions());
|
||||
mRegionalEffects.setValue(monster.getRegionalActionDescriptions());
|
||||
mSavingThrows.setValue(monster.getSavingThrowsDescription());
|
||||
mSenses.setValue(monster.getSensesDescription());
|
||||
mSkills.setValue(monster.getSkillsDescription());
|
||||
mSpeed.setValue(mMonster.getSpeedText());
|
||||
mStrength.setValue(monster.getStrengthDescription());
|
||||
mWisdom.setValue(monster.getWisdomDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.library.LibraryFragmentDirections;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MonsterImportFragment extends MCFragment {
|
||||
private ViewHolder mHolder;
|
||||
private MonsterImportViewModel mViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
Logger.logDebug("MonsterCards: loading monster for import");
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
String json = MonsterImportFragmentArgs.fromBundle(arguments).getJson();
|
||||
setHasOptionsMenu(true);
|
||||
Monster monster = MonsterImportHelper.fromJSON(json);
|
||||
mViewModel = new ViewModelProvider(this).get(MonsterImportViewModel.class);
|
||||
mViewModel.setMonster(monster);
|
||||
View root = inflater.inflate(R.layout.fragment_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getName().observe(getViewLifecycleOwner(), mHolder.name::setText);
|
||||
mViewModel.getMeta().observe(getViewLifecycleOwner(), mHolder.meta::setText);
|
||||
mViewModel.getArmorClass().observe(getViewLifecycleOwner(), armorText -> setupLabeledTextView(mHolder.armorClass, armorText, R.string.label_armor_class));
|
||||
mViewModel.getHitPoints().observe(getViewLifecycleOwner(), hitPoints -> setupLabeledTextView(mHolder.hitPoints, hitPoints, R.string.label_hit_points));
|
||||
mViewModel.getSpeed().observe(getViewLifecycleOwner(), speed -> setupLabeledTextView(mHolder.speed, speed, R.string.label_speed));
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), mHolder.strength::setText);
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), mHolder.dexterity::setText);
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), mHolder.constitution::setText);
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), mHolder.intelligence::setText);
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), mHolder.wisdom::setText);
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), mHolder.charisma::setText);
|
||||
mViewModel.getSavingThrows().observe(getViewLifecycleOwner(), savingThrows -> setupOptionalTextView(mHolder.savingThrows, savingThrows, R.string.label_saving_throws));
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> setupOptionalTextView(mHolder.skills, skills, R.string.label_skills));
|
||||
mViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageVulnerabilities, damageTypes, R.string.label_damage_vulnerabilities));
|
||||
mViewModel.getDamageResistances().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageResistances, damageTypes, R.string.label_damage_resistances));
|
||||
mViewModel.getDamageImmunities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageImmunities, damageTypes, R.string.label_damage_immunities));
|
||||
mViewModel.getConditionImmunities().observe(getViewLifecycleOwner(), conditionImmunities -> setupOptionalTextView(mHolder.conditionImmunities, conditionImmunities, R.string.label_condition_immunities));
|
||||
mViewModel.getSenses().observe(getViewLifecycleOwner(), senses -> setupOptionalTextView(mHolder.senses, senses, R.string.label_senses));
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> setupOptionalTextView(mHolder.languages, languages, R.string.label_languages));
|
||||
mViewModel.getChallenge().observe(getViewLifecycleOwner(), challengeRating -> setupLabeledTextView(mHolder.challenge, challengeRating, R.string.label_challenge_rating));
|
||||
mViewModel.getAbilities().observe(getViewLifecycleOwner(), abilities -> setupTraitList(mHolder.abilities, abilities));
|
||||
mViewModel.getActions().observe(getViewLifecycleOwner(), actions -> setupTraitList(mHolder.actions, actions, mHolder.actions_label, mHolder.actions_divider));
|
||||
mViewModel.getReactions().observe(getViewLifecycleOwner(), reactions -> setupTraitList(mHolder.reactions, reactions, mHolder.reactions_label, mHolder.reactions_divider));
|
||||
mViewModel.getRegionalEffects().observe(getViewLifecycleOwner(), regionalEffects -> setupTraitList(mHolder.regionalEffects, regionalEffects, mHolder.regionalEffects_label, mHolder.regionalEffects_divider));
|
||||
mViewModel.getLairActions().observe(getViewLifecycleOwner(), lairActions -> setupTraitList(mHolder.lairActions, lairActions, mHolder.lairActions_label, mHolder.lairActions_divider));
|
||||
mViewModel.getLegendaryActions().observe(getViewLifecycleOwner(), legendaryActions -> setupTraitList(mHolder.legendaryActions, legendaryActions, mHolder.legendaryActions_label, mHolder.legendaryActions_divider));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupLabeledTextView(TextView view, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
String fullText = String.format("<b>%s</b> %s", title, text);
|
||||
view.setText(Html.fromHtml(fullText));
|
||||
}
|
||||
|
||||
private void setupOptionalTextView(TextView root, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
if (StringHelper.isNullOrEmpty(text)) {
|
||||
root.setVisibility(View.GONE);
|
||||
} else {
|
||||
root.setVisibility(View.VISIBLE);
|
||||
}
|
||||
Spanned formatted;
|
||||
if (StringHelper.isNullOrEmpty(title)) {
|
||||
formatted = Html.fromHtml(text);
|
||||
} else {
|
||||
formatted = Html.fromHtml(String.format("<b>%s</b> %s", title, text));
|
||||
}
|
||||
root.setText(formatted);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits) {
|
||||
setupTraitList(root, traits, null, null);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits, View label, View divider) {
|
||||
int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE;
|
||||
Context context = getContext();
|
||||
DisplayMetrics displayMetrics = null;
|
||||
if (context != null) {
|
||||
Resources resources = context.getResources();
|
||||
if (resources != null) {
|
||||
displayMetrics = resources.getDisplayMetrics();
|
||||
}
|
||||
}
|
||||
root.removeAllViews();
|
||||
for (String action : traits) {
|
||||
TextView tvAction = new TextView(getContext());
|
||||
// TODO: Handle multiline block quotes specially so they stay multiline.
|
||||
// TODO: Replace QuoteSpans in the result of fromHtml with something like this https://stackoverflow.com/questions/7717567/how-to-style-blockquotes-in-android-textviews to make them indent as expected
|
||||
tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
|
||||
tvAction.setLayoutParams(layoutParams);
|
||||
root.addView(tvAction);
|
||||
}
|
||||
root.setVisibility(visibility);
|
||||
if (label != null) {
|
||||
label.setVisibility(visibility);
|
||||
}
|
||||
if (divider != null) {
|
||||
divider.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.import_monster, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_action_import_monster) {
|
||||
Logger.logDebug("Menu Item Selected");
|
||||
Monster monster = mViewModel.getMonster();
|
||||
if (monster != null) {
|
||||
monster.id = UUID.randomUUID();
|
||||
MonsterCardsApplication application = (MonsterCardsApplication) getApplication();
|
||||
MonsterRepository repository = application.getMonsterRepository();
|
||||
repository.addMonster(monster).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Snackbar.make(
|
||||
mHolder.root,
|
||||
getString(R.string.snackbar_monster_created, monster.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", (_view) -> navigateToEditMonster(monster.id))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
Snackbar.make(mHolder.root, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void navigateToEditMonster(UUID monsterId) {
|
||||
NavController navController = Navigation.findNavController(requireView());
|
||||
NavDirections action;
|
||||
action = MonsterImportFragmentDirections.actionMonsterImportFragmentToNavigationLibrary();
|
||||
navController.navigate(action);
|
||||
action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
|
||||
navController.navigate(action);
|
||||
action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
|
||||
navController.navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final View root;
|
||||
final TextView name;
|
||||
final TextView meta;
|
||||
final TextView armorClass;
|
||||
final TextView hitPoints;
|
||||
final TextView speed;
|
||||
final TextView strength;
|
||||
final TextView dexterity;
|
||||
final TextView constitution;
|
||||
final TextView intelligence;
|
||||
final TextView wisdom;
|
||||
final TextView charisma;
|
||||
final TextView savingThrows;
|
||||
final TextView skills;
|
||||
final TextView damageVulnerabilities;
|
||||
final TextView damageResistances;
|
||||
final TextView damageImmunities;
|
||||
final TextView conditionImmunities;
|
||||
final TextView senses;
|
||||
final TextView languages;
|
||||
final TextView challenge;
|
||||
final LinearLayout abilities;
|
||||
final LinearLayout actions;
|
||||
final TextView actions_label;
|
||||
final ImageView actions_divider;
|
||||
final LinearLayout reactions;
|
||||
final TextView reactions_label;
|
||||
final ImageView reactions_divider;
|
||||
final LinearLayout legendaryActions;
|
||||
final TextView legendaryActions_label;
|
||||
final ImageView legendaryActions_divider;
|
||||
final LinearLayout lairActions;
|
||||
final TextView lairActions_label;
|
||||
final ImageView lairActions_divider;
|
||||
final LinearLayout regionalEffects;
|
||||
final TextView regionalEffects_label;
|
||||
final ImageView regionalEffects_divider;
|
||||
|
||||
ViewHolder(View root) {
|
||||
this.root = root;
|
||||
name = root.findViewById(R.id.name);
|
||||
meta = root.findViewById(R.id.meta);
|
||||
armorClass = root.findViewById(R.id.armorClass);
|
||||
hitPoints = root.findViewById(R.id.hitPoints);
|
||||
speed = root.findViewById(R.id.speed);
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challenge = root.findViewById(R.id.challenge);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
actions_divider = root.findViewById(R.id.actions_divider);
|
||||
actions_label = root.findViewById(R.id.actions_label);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
reactions_divider = root.findViewById(R.id.reactions_divider);
|
||||
reactions_label = root.findViewById(R.id.reactions_label);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
legendaryActions_divider = root.findViewById(R.id.legendaryActions_divider);
|
||||
legendaryActions_label = root.findViewById(R.id.legendaryActions_label);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
lairActions_divider = root.findViewById(R.id.lairActions_divider);
|
||||
lairActions_label = root.findViewById(R.id.lairActions_label);
|
||||
regionalEffects = root.findViewById(R.id.regionalEffects);
|
||||
regionalEffects_divider = root.findViewById(R.id.regionalEffects_divider);
|
||||
regionalEffects_label = root.findViewById(R.id.regionalEffects_label);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MonsterImportViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<String>> mAbilities;
|
||||
private final MutableLiveData<List<String>> mActions;
|
||||
private final MutableLiveData<String> mArmorClass;
|
||||
private final MutableLiveData<String> mChallenge;
|
||||
private final MutableLiveData<String> mCharisma;
|
||||
private final MutableLiveData<String> mConditionImmunities;
|
||||
private final MutableLiveData<String> mConstitution;
|
||||
private final MutableLiveData<String> mDamageResistances;
|
||||
private final MutableLiveData<String> mDamageImmunities;
|
||||
private final MutableLiveData<String> mDamageVulnerabilities;
|
||||
private final MutableLiveData<String> mDexterity;
|
||||
private final MutableLiveData<String> mHitPoints;
|
||||
private final MutableLiveData<String> mIntelligence;
|
||||
private final MutableLiveData<List<String>> mLairActions;
|
||||
private final MutableLiveData<String> mLanguages;
|
||||
private final MutableLiveData<List<String>> mLegendaryActions;
|
||||
private final MutableLiveData<String> mMeta;
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<List<String>> mReactions;
|
||||
private final MutableLiveData<List<String>> mRegionalEffects;
|
||||
private final MutableLiveData<String> mSavingThrows;
|
||||
private final MutableLiveData<String> mSenses;
|
||||
private final MutableLiveData<String> mSkills;
|
||||
private final MutableLiveData<String> mSpeed;
|
||||
private final MutableLiveData<String> mStrength;
|
||||
private final MutableLiveData<String> mWisdom;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
private Monster mMonster;
|
||||
|
||||
public MonsterImportViewModel() {
|
||||
mMonster = null;
|
||||
mAbilities = new MutableLiveData<>(new ArrayList<>());
|
||||
mActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mArmorClass = new MutableLiveData<>("");
|
||||
mChallenge = new MutableLiveData<>("");
|
||||
mCharisma = new MutableLiveData<>("");
|
||||
mConditionImmunities = new MutableLiveData<>("");
|
||||
mConstitution = new MutableLiveData<>("");
|
||||
mDamageImmunities = new MutableLiveData<>("");
|
||||
mDamageResistances = new MutableLiveData<>("");
|
||||
mDamageVulnerabilities = new MutableLiveData<>("");
|
||||
mDexterity = new MutableLiveData<>("");
|
||||
mHitPoints = new MutableLiveData<>("");
|
||||
mIntelligence = new MutableLiveData<>("");
|
||||
mLairActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mLanguages = new MutableLiveData<>("");
|
||||
mLegendaryActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mMeta = new MutableLiveData<>("");
|
||||
mName = new MutableLiveData<>("");
|
||||
mReactions = new MutableLiveData<>(new ArrayList<>());
|
||||
mRegionalEffects = new MutableLiveData<>(new ArrayList<>());
|
||||
mSavingThrows = new MutableLiveData<>("");
|
||||
mSenses = new MutableLiveData<>("");
|
||||
mSkills = new MutableLiveData<>("");
|
||||
mSpeed = new MutableLiveData<>("");
|
||||
mStrength = new MutableLiveData<>("");
|
||||
mWisdom = new MutableLiveData<>("");
|
||||
mMonsterId = new MutableLiveData<>(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getAbilities() {
|
||||
return mAbilities;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getActions() {
|
||||
return mActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getReactions() {
|
||||
return mReactions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLegendaryActions() {
|
||||
return mLegendaryActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLairActions() {
|
||||
return mLairActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getRegionalEffects() {
|
||||
return mRegionalEffects;
|
||||
}
|
||||
|
||||
public LiveData<String> getArmorClass() {
|
||||
return mArmorClass;
|
||||
}
|
||||
|
||||
public LiveData<String> getChallenge() {
|
||||
return mChallenge;
|
||||
}
|
||||
|
||||
public LiveData<String> getCharisma() {
|
||||
return mCharisma;
|
||||
}
|
||||
|
||||
public LiveData<String> getConditionImmunities() {
|
||||
return mConditionImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getConstitution() {
|
||||
return mConstitution;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageResistances() {
|
||||
return mDamageResistances;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageImmunities() {
|
||||
return mDamageImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageVulnerabilities() {
|
||||
return mDamageVulnerabilities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDexterity() {
|
||||
return mDexterity;
|
||||
}
|
||||
|
||||
public LiveData<String> getHitPoints() {
|
||||
return mHitPoints;
|
||||
}
|
||||
|
||||
public LiveData<String> getIntelligence() {
|
||||
return mIntelligence;
|
||||
}
|
||||
|
||||
public LiveData<String> getLanguages() {
|
||||
return mLanguages;
|
||||
}
|
||||
|
||||
public LiveData<String> getMeta() {
|
||||
return mMeta;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public LiveData<String> getSavingThrows() {
|
||||
return mSavingThrows;
|
||||
}
|
||||
|
||||
public LiveData<String> getSenses() {
|
||||
return mSenses;
|
||||
}
|
||||
|
||||
public LiveData<String> getSkills() {
|
||||
return mSkills;
|
||||
}
|
||||
|
||||
public LiveData<String> getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
public LiveData<String> getStrength() {
|
||||
return mStrength;
|
||||
}
|
||||
|
||||
public LiveData<String> getWisdom() {
|
||||
return mWisdom;
|
||||
}
|
||||
|
||||
public LiveData<UUID> getId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
|
||||
public Monster getMonster() {
|
||||
return mMonster;
|
||||
}
|
||||
|
||||
public void setMonster(Monster monster) {
|
||||
mMonster = monster;
|
||||
mAbilities.setValue(mMonster.getAbilityDescriptions());
|
||||
mActions.setValue(mMonster.getActionDescriptions());
|
||||
mArmorClass.setValue(mMonster.getArmorClass());
|
||||
mChallenge.setValue(mMonster.getChallengeRatingDescription());
|
||||
mCharisma.setValue(monster.getCharismaDescription());
|
||||
mConditionImmunities.setValue(mMonster.getConditionImmunitiesDescription());
|
||||
mConstitution.setValue(monster.getConstitutionDescription());
|
||||
mDamageImmunities.setValue(mMonster.getDamageImmunitiesDescription());
|
||||
mDamageResistances.setValue(mMonster.getDamageResistancesDescription());
|
||||
mDamageVulnerabilities.setValue(mMonster.getDamageVulnerabilitiesDescription());
|
||||
mDexterity.setValue(monster.getDexterityDescription());
|
||||
mHitPoints.setValue(mMonster.getHitPoints());
|
||||
mIntelligence.setValue(monster.getIntelligenceDescription());
|
||||
mLairActions.setValue(mMonster.getLairActionDescriptions());
|
||||
mLanguages.setValue(mMonster.getLanguagesDescription());
|
||||
mLegendaryActions.setValue(mMonster.getLegendaryActionDescriptions());
|
||||
mMeta.setValue(mMonster.getMeta());
|
||||
mMonsterId.setValue(mMonster.id);
|
||||
mName.setValue(mMonster.name);
|
||||
mReactions.setValue(monster.getReactionDescriptions());
|
||||
mRegionalEffects.setValue(monster.getRegionalActionDescriptions());
|
||||
mSavingThrows.setValue(monster.getSavingThrowsDescription());
|
||||
mSenses.setValue(monster.getSensesDescription());
|
||||
mSkills.setValue(monster.getSkillsDescription());
|
||||
mSpeed.setValue(mMonster.getSpeedText());
|
||||
mStrength.setValue(monster.getStrengthDescription());
|
||||
mWisdom.setValue(monster.getWisdomDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.majinnaibu.monstercards.ui.search;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
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.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentSearchBinding;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SearchFragment extends MCFragment {
|
||||
private SearchViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private SearchResultsRecyclerViewAdapter mAdapter;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(SearchViewModel.class);
|
||||
FragmentSearchBinding binding = FragmentSearchBinding.inflate(inflater, container, false);
|
||||
mHolder = new ViewHolder(binding);
|
||||
// TODO: set the title with setTitle(...)
|
||||
setupMonsterList(binding.monsterList);
|
||||
setupFilterBox(binding.searchQuery);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupFilterBox(@NonNull TextView textBox) {
|
||||
textBox.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
mViewModel.setFilterText(textBox.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupMonsterList(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.majinnaibu.monstercards.ui.search;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.SimpleListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.SimpleListItemViewHolder;
|
||||
import com.majinnaibu.monstercards.utils.ItemCallback;
|
||||
|
||||
public class SearchResultsRecyclerViewAdapter extends ListAdapter<Monster, SimpleListItemViewHolder<Monster>> {
|
||||
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return Monster.areItemsTheSame(oldItem, newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return Monster.areContentsTheSame(oldItem, newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback<Monster> mOnClick;
|
||||
|
||||
public SearchResultsRecyclerViewAdapter(ItemCallback<Monster> onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SimpleListItemViewHolder<Monster> onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
SimpleListItemBinding binding = SimpleListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
||||
return new SimpleListItemViewHolder<>(binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final SimpleListItemViewHolder<Monster> holder, int position) {
|
||||
Monster monster = getItem(position);
|
||||
holder.item = monster;
|
||||
holder.contentView.setText(monster.name);
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItem(holder.item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class ChangeTrackedViewModel extends ViewModel {
|
||||
private final MutableLiveData<Boolean> mHasChanges;
|
||||
|
||||
public ChangeTrackedViewModel() {
|
||||
mHasChanges = new MutableLiveData<>(false);
|
||||
}
|
||||
|
||||
public boolean hasChanges() {
|
||||
Boolean value = mHasChanges.getValue();
|
||||
return value != null && value;
|
||||
}
|
||||
|
||||
protected void makeDirty() {
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
protected void makeClean() {
|
||||
mHasChanges.setValue(false);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user