Squashed 'Android/' content from commit 7a63a11
git-subtree-dir: Android
git-subtree-split: 7a63a11e93
This commit is contained in:
1
app/.gitignore
vendored
Normal file
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
109
app/build.gradle
Normal file
109
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.3'
|
||||
|
||||
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.3.1'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
|
||||
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.2.1'
|
||||
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.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.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
app/proguard-rules.pro
vendored
Normal file
32
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);
|
||||
}
|
||||
440
app/schemas/com.majinnaibu.monstercards.AppDatabase/1.json
Normal file
440
app/schemas/com.majinnaibu.monstercards.AppDatabase/1.json
Normal file
@@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
499
app/schemas/com.majinnaibu.monstercards.AppDatabase/2.json
Normal file
499
app/schemas/com.majinnaibu.monstercards.AppDatabase/2.json
Normal file
@@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
483
app/schemas/com.majinnaibu.monstercards.AppDatabase/3.json
Normal file
483
app/schemas/com.majinnaibu.monstercards.AppDatabase/3.json
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
59
app/src/main/AndroidManifest.xml
Normal file
59
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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>
|
||||
<activity
|
||||
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.TypeConverters;
|
||||
|
||||
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.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,
|
||||
SetOfSkillConverter.class,
|
||||
SetOfStringConverter.class,
|
||||
UUIDConverter.class,
|
||||
})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract MonsterDAO monsterDAO();
|
||||
}
|
||||
118
app/src/main/java/com/majinnaibu/monstercards/MainActivity.java
Normal file
118
app/src/main/java/com/majinnaibu/monstercards/MainActivity.java
Normal file
@@ -0,0 +1,118 @@
|
||||
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.annotation.Nullable;
|
||||
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(FlipperInitializer::sendNavigationEvent);
|
||||
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 = Objects.requireNonNull((NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment));
|
||||
NavController navController = navHostFragment.getNavController();
|
||||
NavDirections action = MobileNavigationDirections.actionGlobalMonsterImportFragment(json);
|
||||
navController.navigate(action);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String readMonsterJSONFromIntent(@NonNull 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", action, type));
|
||||
}
|
||||
if (uri == null) {
|
||||
return null;
|
||||
}
|
||||
json = readContentsOfUri(uri);
|
||||
if (StringHelper.isNullOrEmpty(json)) {
|
||||
return null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
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,82 @@
|
||||
package com.majinnaibu.monstercards;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.migration.Migration;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.init.FlipperInitializer;
|
||||
|
||||
public class MonsterCardsApplication extends Application {
|
||||
|
||||
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
// rename table monster to monsters
|
||||
database.execSQL("ALTER TABLE monster RENAME TO monsters");
|
||||
// create the fts view
|
||||
database.execSQL("CREATE VIRTUAL TABLE IF NOT EXISTS `monsters_fts` USING FTS4(`name` TEXT, `size` TEXT, `type` TEXT, `subtype` TEXT, `alignment` TEXT, content=`monsters`)");
|
||||
// build the initial full text search index
|
||||
database.execSQL("INSERT INTO monsters_fts(monsters_fts) VALUES('rebuild')");
|
||||
|
||||
}
|
||||
};
|
||||
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
|
||||
@Override
|
||||
public void migrate(@NonNull SupportSQLiteDatabase database) {
|
||||
// Add the senses column
|
||||
database.execSQL("ALTER TABLE monsters ADD COLUMN 'senses' TEXT DEFAULT '[]'");
|
||||
database.execSQL("CREATE TABLE new_monsters (`id` TEXT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `size` TEXT NOT NULL DEFAULT '', `type` TEXT NOT NULL DEFAULT '', `subtype` TEXT NOT NULL DEFAULT '', `alignment` TEXT NOT NULL DEFAULT '', `strength_score` INTEGER NOT NULL DEFAULT 10, `strength_saving_throw_advantage` TEXT DEFAULT 'none', `strength_saving_throw_proficiency` TEXT DEFAULT 'none', `dexterity_score` INTEGER NOT NULL DEFAULT 10, `dexterity_saving_throw_advantage` TEXT DEFAULT 'none', `dexterity_saving_throw_proficiency` TEXT DEFAULT 'none', `constitution_score` INTEGER NOT NULL DEFAULT 10, `constitution_saving_throw_advantage` TEXT DEFAULT 'none', `constitution_saving_throw_proficiency` TEXT DEFAULT 'none', `intelligence_score` INTEGER NOT NULL DEFAULT 10, `intelligence_saving_throw_advantage` TEXT DEFAULT 'none', `intelligence_saving_throw_proficiency` TEXT DEFAULT 'none', `wisdom_score` INTEGER NOT NULL DEFAULT 10, `wisdom_saving_throw_advantage` TEXT DEFAULT 'none', `wisdom_saving_throw_proficiency` TEXT DEFAULT 'none', `charisma_score` INTEGER NOT NULL DEFAULT 10, `charisma_saving_throw_advantage` TEXT DEFAULT 'none', `charisma_saving_throw_proficiency` TEXT DEFAULT 'none', `armor_type` TEXT DEFAULT 'none', `shield_bonus` INTEGER NOT NULL DEFAULT 0, `natural_armor_bonus` INTEGER NOT NULL DEFAULT 0, `other_armor_description` TEXT DEFAULT '', `hit_dice` INTEGER NOT NULL DEFAULT 1, `has_custom_hit_points` INTEGER NOT NULL, `custom_hit_points_description` TEXT DEFAULT '', `walk_speed` INTEGER NOT NULL DEFAULT 0, `burrow_speed` INTEGER NOT NULL DEFAULT 0, `climb_speed` INTEGER NOT NULL DEFAULT 0, `fly_speed` INTEGER NOT NULL DEFAULT 0, `can_hover` INTEGER NOT NULL DEFAULT false, `swim_speed` INTEGER NOT NULL DEFAULT 0, `has_custom_speed` INTEGER NOT NULL DEFAULT false, `custom_speed_description` TEXT, `challenge_rating` TEXT DEFAULT '1', `custom_challenge_rating_description` TEXT DEFAULT '', `custom_proficiency_bonus` INTEGER NOT NULL DEFAULT 0, `telepathy_range` INTEGER NOT NULL DEFAULT 0, `understands_but_description` TEXT DEFAULT '', `senses` TEXT DEFAULT '[]', `skills` TEXT DEFAULT '[]', `damage_immunities` TEXT DEFAULT '[]', `damage_resistances` TEXT DEFAULT '[]', `damage_vulnerabilities` TEXT DEFAULT '[]', `condition_immunities` TEXT DEFAULT '[]', `languages` TEXT DEFAULT '[]', `abilities` TEXT DEFAULT '[]', `actions` TEXT DEFAULT '[]', `reactions` TEXT DEFAULT '[]', `lair_actions` TEXT DEFAULT '[]', `legendary_actions` TEXT DEFAULT '[]', `regional_actions` TEXT DEFAULT '[]', PRIMARY KEY(`id`))");
|
||||
database.execSQL("INSERT INTO new_monsters(id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions) SELECT id, name, size, type, subtype, alignment, strength_score, strength_saving_throw_advantage, strength_saving_throw_proficiency, dexterity_score, dexterity_saving_throw_advantage, dexterity_saving_throw_proficiency, constitution_score, constitution_saving_throw_advantage, constitution_saving_throw_proficiency, intelligence_score, intelligence_saving_throw_advantage, intelligence_saving_throw_proficiency, wisdom_score, wisdom_saving_throw_advantage, wisdom_saving_throw_proficiency, charisma_score, charisma_saving_throw_advantage, charisma_saving_throw_proficiency, armor_type, shield_bonus, natural_armor_bonus, other_armor_description, hit_dice, has_custom_hit_points, custom_hit_points_description, walk_speed, burrow_speed, climb_speed, fly_speed, can_hover, swim_speed, has_custom_speed, custom_speed_description, challenge_rating, custom_challenge_rating_description, custom_proficiency_bonus, telepathy_range, understands_but_description, senses, skills, damage_immunities, damage_resistances, damage_vulnerabilities, condition_immunities, languages, abilities, actions, reactions, lair_actions, legendary_actions, regional_actions FROM monsters");
|
||||
database.execSQL("DROP TABLE monsters");
|
||||
database.execSQL("ALTER TABLE new_monsters RENAME TO monsters");
|
||||
}
|
||||
};
|
||||
private MonsterRepository m_monsterLibraryRepository;
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// .fallbackToDestructiveMigration()
|
||||
AppDatabase m_db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "monsters")
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.addMigrations(MIGRATION_2_3)
|
||||
.fallbackToDestructiveMigrationOnDowngrade()
|
||||
// .fallbackToDestructiveMigration()
|
||||
.build();
|
||||
m_monsterLibraryRepository = new MonsterRepository(m_db);
|
||||
}
|
||||
|
||||
// Called by the system when the device configuration changes while your component is running.
|
||||
// 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,113 @@
|
||||
package com.majinnaibu.monstercards.data;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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 {
|
||||
@NonNull
|
||||
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,112 @@
|
||||
package com.majinnaibu.monstercards.data;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.majinnaibu.monstercards.AppDatabase;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public class MonsterRepository {
|
||||
|
||||
private final 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()
|
||||
.getAll()
|
||||
.map(monsters -> {
|
||||
ArrayList<Monster> filteredMonsters = new ArrayList<>();
|
||||
for (Monster monster : monsters) {
|
||||
if (Helpers.monsterMatchesSearch(monster, searchText)) {
|
||||
filteredMonsters.add(monster);
|
||||
}
|
||||
}
|
||||
return (List<Monster>) filteredMonsters;
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread());
|
||||
}
|
||||
|
||||
public Flowable<Monster> getMonster(@NonNull 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;
|
||||
}
|
||||
|
||||
private static class Helpers {
|
||||
static boolean monsterMatchesSearch(Monster monster, String searchText) {
|
||||
if (StringHelper.isNullOrEmpty(searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringHelper.containsCaseInsensitive(monster.name, searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringHelper.containsCaseInsensitive(monster.size, searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringHelper.containsCaseInsensitive(monster.type, searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringHelper.containsCaseInsensitive(monster.subtype, searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringHelper.containsCaseInsensitive(monster.alignment, searchText)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
|
||||
public class ArmorTypeConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromArmorType(@NonNull ArmorType armorType) {
|
||||
return armorType.stringValue;
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static ArmorType armorTypeFromStringValue(String stringValue) {
|
||||
return ArmorType.valueOfString(stringValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
|
||||
public class ChallengeRatingConverter {
|
||||
|
||||
@TypeConverter
|
||||
public static String fromChallengeRating(@NonNull 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,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,20 @@
|
||||
package com.majinnaibu.monstercards.data.converters;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.TypeConverter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDConverter {
|
||||
|
||||
@NonNull
|
||||
@TypeConverter
|
||||
public static String fromUUID(@NonNull 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("intelligence", "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("expertise", "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,17 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ArrayHelper {
|
||||
public static int indexOf(@NonNull 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,27 @@
|
||||
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.html.HtmlRenderer;
|
||||
|
||||
public final class CommonMarkHelper {
|
||||
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,311 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class MonsterImportHelper {
|
||||
@NonNull
|
||||
public static Monster fromJSON(String json) {
|
||||
JsonObject rootDict = JsonParser.parseString(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(@NonNull 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(@NonNull JsonObject dict, String name, int defaultValue) {
|
||||
if (dict.has(name)) {
|
||||
JsonElement element = dict.get(name);
|
||||
if (element.isJsonPrimitive()) {
|
||||
JsonPrimitive rawValue = element.getAsJsonPrimitive();
|
||||
if (rawValue.isNumber()) {
|
||||
return rawValue.getAsInt();
|
||||
} else {
|
||||
try {
|
||||
return rawValue.getAsInt();
|
||||
} catch (Exception ex) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static boolean getBool(JsonObject dict, String name) {
|
||||
return getBool(dict, name, false);
|
||||
}
|
||||
|
||||
public static boolean getBool(@NonNull 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();
|
||||
} else {
|
||||
try {
|
||||
return rawValue.getAsBoolean();
|
||||
} catch (Exception ex) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String formatDistance(String name, int distance) {
|
||||
// TODO: consider moving this to a string resource so it can be localized
|
||||
return String.format(Locale.getDefault(), "%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));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<Trait> getListOfTraits(@NonNull 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, "desc");
|
||||
Trait trait = new Trait(traitName, description);
|
||||
traits.add(trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
||||
public static void addSavingThrows(Monster monster, @NonNull 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Set<Skill> getSetOfSkills(@NonNull 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;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Set<String> getSetOfDamageTypes(JsonObject rootDict, String name) {
|
||||
return getSetOfDamageTypes(rootDict, name, null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Set<String> getSetOfDamageTypes(@NonNull 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;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Set<Language> getSetOfLanguages(@NonNull 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,79 @@
|
||||
package com.majinnaibu.monstercards.helpers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@SuppressWarnings({"RedundantIfStatement"})
|
||||
public final class StringHelper {
|
||||
public static boolean isNullOrEmpty(CharSequence value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ("".contentEquals(value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String join(String delimiter, @NonNull Collection<String> strings) {
|
||||
int length = strings.size();
|
||||
if (length < 1) {
|
||||
return "";
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean isFirst = true;
|
||||
for (String element : strings) {
|
||||
if (!isFirst) {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
|
||||
sb.append(element);
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static String oxfordJoin(String delimiter, String lastDelimiter, String onlyDelimiter, @NonNull Collection<String> strings) {
|
||||
int length = strings.size();
|
||||
if (length < 1) {
|
||||
return "";
|
||||
} else if (length == 2) {
|
||||
return join(onlyDelimiter, strings);
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int index = 0;
|
||||
int lastIndex = length - 1;
|
||||
for (String element : strings) {
|
||||
if (index > 0 && index < lastIndex) {
|
||||
sb.append(delimiter);
|
||||
} else if (index > 0 && index >= lastIndex) {
|
||||
sb.append(lastDelimiter);
|
||||
}
|
||||
sb.append(element);
|
||||
index++;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Integer parseInt(String s) {
|
||||
try {
|
||||
return Integer.parseInt(s);
|
||||
} catch (NumberFormatException _ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsCaseInsensitive(@NonNull String text, @NonNull String search) {
|
||||
// TODO: find a locale independent way to do this
|
||||
return text.toLowerCase().contains(search.toLowerCase());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Language implements Comparator<Language>, Comparable<Language> {
|
||||
|
||||
private String mName;
|
||||
private boolean mSpeaks;
|
||||
|
||||
public Language(String name, boolean speaks) {
|
||||
mName = name;
|
||||
mSpeaks = speaks;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String value) {
|
||||
mName = value;
|
||||
}
|
||||
|
||||
public boolean getSpeaks() {
|
||||
return mSpeaks;
|
||||
}
|
||||
|
||||
public void setSpeaks(boolean value) {
|
||||
mSpeaks = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Language o) {
|
||||
if (this.mSpeaks && !o.mSpeaks) {
|
||||
return -1;
|
||||
}
|
||||
if (!this.mSpeaks && o.mSpeaks) {
|
||||
return 1;
|
||||
}
|
||||
return this.mName.compareToIgnoreCase(o.mName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(@NonNull Language o1, Language o2) {
|
||||
if (o1.mSpeaks && !o2.mSpeaks) {
|
||||
return -1;
|
||||
}
|
||||
if (!o1.mSpeaks && o2.mSpeaks) {
|
||||
return 1;
|
||||
}
|
||||
return o1.mName.compareToIgnoreCase(o2.mName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof Language)) {
|
||||
return false;
|
||||
}
|
||||
Language otherLanguage = (Language) obj;
|
||||
if (!Objects.equals(this.mName, otherLanguage.mName)) {
|
||||
return false;
|
||||
}
|
||||
if (this.mSpeaks != otherLanguage.mSpeaks) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
1070
app/src/main/java/com/majinnaibu/monstercards/models/Monster.java
Normal file
1070
app/src/main/java/com/majinnaibu/monstercards/models/Monster.java
Normal file
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,95 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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;
|
||||
import java.util.Objects;
|
||||
|
||||
@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.charAt(0),
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof Skill)) {
|
||||
return false;
|
||||
}
|
||||
Skill otherSkill = (Skill) obj;
|
||||
if (!Objects.equals(this.name, otherSkill.name)) {
|
||||
return false;
|
||||
}
|
||||
if (this.abilityScore != otherSkill.abilityScore) {
|
||||
return false;
|
||||
}
|
||||
if (this.advantageType != otherSkill.advantageType) {
|
||||
return false;
|
||||
}
|
||||
if (this.proficiencyType != otherSkill.proficiencyType) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.majinnaibu.monstercards.models;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Trait implements Comparator<Trait>, Comparable<Trait> {
|
||||
|
||||
public String name;
|
||||
public String description;
|
||||
|
||||
public Trait(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Trait o) {
|
||||
return compare(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Trait o1, Trait o2) {
|
||||
int result = o1.name.compareToIgnoreCase(o2.name);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
return o1.description.compareToIgnoreCase(o2.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof Trait)) {
|
||||
return false;
|
||||
}
|
||||
Trait otherTrait = (Trait) obj;
|
||||
if (!Objects.equals(this.name, otherTrait.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.description, otherTrait.description)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class CollectionsFragment extends MCFragment {
|
||||
|
||||
private CollectionsViewModel collectionsViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
collectionsViewModel = new ViewModelProvider(this).get(CollectionsViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_collections, container, false);
|
||||
final TextView textView = root.findViewById(R.id.text_collections);
|
||||
collectionsViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.majinnaibu.monstercards.ui.collections;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class CollectionsViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<String> mText;
|
||||
|
||||
public CollectionsViewModel() {
|
||||
mText = new MutableLiveData<>();
|
||||
mText.setValue("This is collections fragment");
|
||||
}
|
||||
|
||||
public LiveData<String> getText() {
|
||||
return mText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class AbilityScorePicker extends LinearLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private AbilityScore mSelectedValue;
|
||||
private String mLabel;
|
||||
|
||||
public AbilityScorePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = AbilityScore.STRENGTH;
|
||||
mOnValueChangedListener = null;
|
||||
// TODO: use this as default but allow setting via attribute
|
||||
mLabel = "Ability Score";
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AbilityScorePicker, 0, 0);
|
||||
String label = a.getString(R.styleable.AbilityScorePicker_label);
|
||||
if (label != null) {
|
||||
mLabel = label;
|
||||
}
|
||||
a.recycle();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_ability_score_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.label.setText(mLabel);
|
||||
|
||||
mHolder.spinner.setAdapter(new ArrayAdapter<AbilityScore>(getContext(), R.layout.dropdown_list_item, AbilityScore.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
AbilityScore item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
AbilityScore item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
setValue((AbilityScore) parent.getItemAtPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
setValue(mSelectedValue = AbilityScore.STRENGTH);
|
||||
}
|
||||
});
|
||||
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), mSelectedValue));
|
||||
|
||||
setValue(AbilityScore.STRENGTH);
|
||||
}
|
||||
|
||||
public AbilityScorePicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AbilityScore getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(AbilityScore value) {
|
||||
if (value != mSelectedValue) {
|
||||
mSelectedValue = value;
|
||||
mHolder.spinner.setSelection(ArrayHelper.indexOf(AbilityScore.values(), value));
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
if (!Objects.equals(mLabel, label)) {
|
||||
mLabel = label;
|
||||
mHolder.label.setText(label);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(AbilityScore value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
private final Spinner spinner;
|
||||
private final TextView label;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
spinner = root.findViewById(R.id.spinner);
|
||||
label = root.findViewById(R.id.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AdvantagePicker extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private AdvantageType mSelectedValue;
|
||||
|
||||
public AdvantagePicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = AdvantageType.NONE;
|
||||
mOnValueChangedListener = null;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_advantage_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(AdvantageType.NONE);
|
||||
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (R.id.hasAdvantage == checkedId) {
|
||||
setValue(AdvantageType.ADVANTAGE);
|
||||
} else if (R.id.hasDisadvantage == checkedId) {
|
||||
setValue(AdvantageType.DISADVANTAGE);
|
||||
} else {
|
||||
setValue(AdvantageType.NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public AdvantagePicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AdvantageType getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(AdvantageType value) {
|
||||
if (mSelectedValue != value) {
|
||||
mSelectedValue = value;
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(mSelectedValue);
|
||||
}
|
||||
}
|
||||
final int checkedId = mHolder.group.getCheckedRadioButtonId();
|
||||
if (mSelectedValue == AdvantageType.ADVANTAGE) {
|
||||
if (checkedId != R.id.hasAdvantage) {
|
||||
mHolder.advantage.setChecked(true);
|
||||
}
|
||||
} else if (mSelectedValue == AdvantageType.DISADVANTAGE) {
|
||||
if (checkedId != R.id.hasDisadvantage) {
|
||||
mHolder.disadvantage.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
if (checkedId != R.id.none) {
|
||||
mHolder.none.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(AdvantageType value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RadioGroup group;
|
||||
final MaterialRadioButton none;
|
||||
final MaterialRadioButton advantage;
|
||||
final MaterialRadioButton disadvantage;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
group = root.findViewById(R.id.group);
|
||||
none = root.findViewById(R.id.hasNoAdvantage);
|
||||
advantage = root.findViewById(R.id.hasAdvantage);
|
||||
disadvantage = root.findViewById(R.id.hasDisadvantage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ProficiencyPicker extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private OnValueChangedListener mOnValueChangedListener;
|
||||
private ProficiencyType mSelectedValue;
|
||||
|
||||
public ProficiencyPicker(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mSelectedValue = ProficiencyType.NONE;
|
||||
mOnValueChangedListener = null;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_proficiency_picker, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(ProficiencyType.NONE);
|
||||
mHolder.group.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (R.id.proficient == checkedId) {
|
||||
setValue(ProficiencyType.PROFICIENT);
|
||||
} else if (R.id.expertise == checkedId) {
|
||||
setValue(ProficiencyType.EXPERTISE);
|
||||
} else {
|
||||
setValue(ProficiencyType.NONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ProficiencyPicker(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public ProficiencyType getValue() {
|
||||
return mSelectedValue;
|
||||
}
|
||||
|
||||
public void setValue(ProficiencyType value) {
|
||||
if (mSelectedValue != value) {
|
||||
mSelectedValue = value;
|
||||
if (mOnValueChangedListener != null) {
|
||||
mOnValueChangedListener.onValueChanged(mSelectedValue);
|
||||
}
|
||||
}
|
||||
final int checkedId = mHolder.group.getCheckedRadioButtonId();
|
||||
if (mSelectedValue == ProficiencyType.PROFICIENT) {
|
||||
if (checkedId != R.id.proficient) {
|
||||
mHolder.proficient.setChecked(true);
|
||||
}
|
||||
} else if (mSelectedValue == ProficiencyType.EXPERTISE) {
|
||||
if (checkedId != R.id.expertise) {
|
||||
mHolder.expertise.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
if (checkedId != R.id.none) {
|
||||
mHolder.none.setChecked(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangedListener(OnValueChangedListener listener) {
|
||||
mOnValueChangedListener = listener;
|
||||
}
|
||||
|
||||
public interface OnValueChangedListener {
|
||||
void onValueChanged(ProficiencyType value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RadioGroup group;
|
||||
final MaterialRadioButton none;
|
||||
final MaterialRadioButton proficient;
|
||||
final MaterialRadioButton expertise;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
group = root.findViewById(R.id.group);
|
||||
none = root.findViewById(R.id.none);
|
||||
proficient = root.findViewById(R.id.proficient);
|
||||
expertise = root.findViewById(R.id.expertise);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.majinnaibu.monstercards.ui.components;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Stepper extends ConstraintLayout {
|
||||
private final ViewHolder mHolder;
|
||||
private int mCurrentValue;
|
||||
private int mStep;
|
||||
private int mMinValue;
|
||||
private int mMaxValue;
|
||||
private String mLabel;
|
||||
private OnValueChangeListener mOnValueChangeListener;
|
||||
private OnFormatValueCallback mOnFormatValueCallback;
|
||||
|
||||
public Stepper(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mCurrentValue = 0;
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Stepper, 0, 0);
|
||||
mStep = a.getInt(R.styleable.Stepper_stepAmount, 1);
|
||||
mMinValue = a.getInt(R.styleable.Stepper_minValue, Integer.MIN_VALUE);
|
||||
mMaxValue = a.getInt(R.styleable.Stepper_maxValue, Integer.MAX_VALUE);
|
||||
mLabel = a.getString(R.styleable.Stepper_label);
|
||||
a.recycle();
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View root = inflater.inflate(R.layout.component_stepper, this, true);
|
||||
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setValue(mCurrentValue);
|
||||
updateDisplayedValue();
|
||||
mHolder.increment.setOnClickListener(v -> setValue(mCurrentValue + mStep));
|
||||
mHolder.decrement.setOnClickListener(v -> setValue(mCurrentValue - mStep));
|
||||
|
||||
mHolder.label.setText(mLabel);
|
||||
}
|
||||
|
||||
public Stepper(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return mLabel;
|
||||
}
|
||||
|
||||
public void setLabel(String newLabel) {
|
||||
if (!Objects.equals(mLabel, newLabel)) {
|
||||
mLabel = newLabel;
|
||||
mHolder.label.setText(mLabel);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return mCurrentValue;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
int oldValue = this.mCurrentValue;
|
||||
int newValue = Math.min(mMaxValue, Math.max(mMinValue, value));
|
||||
if (newValue != oldValue) {
|
||||
this.mCurrentValue = newValue;
|
||||
if (mOnValueChangeListener != null) {
|
||||
mOnValueChangeListener.onChange(newValue, oldValue);
|
||||
}
|
||||
updateDisplayedValue();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDisplayedValue() {
|
||||
if (mOnFormatValueCallback != null) {
|
||||
mHolder.text.setText(mOnFormatValueCallback.onFormatValue(this.mCurrentValue));
|
||||
} else {
|
||||
mHolder.text.setText(String.valueOf(this.mCurrentValue));
|
||||
}
|
||||
}
|
||||
|
||||
public void setOnValueChangeListener(OnValueChangeListener listener) {
|
||||
mOnValueChangeListener = listener;
|
||||
}
|
||||
|
||||
public void setOnFormatValueCallback(OnFormatValueCallback callback) {
|
||||
mOnFormatValueCallback = callback;
|
||||
updateDisplayedValue();
|
||||
}
|
||||
|
||||
public int getStep() {
|
||||
return mStep;
|
||||
}
|
||||
|
||||
public void setStep(int step) {
|
||||
this.mStep = step;
|
||||
}
|
||||
|
||||
public int getMinValue() {
|
||||
return mMinValue;
|
||||
}
|
||||
|
||||
public void setMinValue(int minValue) {
|
||||
this.mMinValue = minValue;
|
||||
}
|
||||
|
||||
public int getMaxValue() {
|
||||
return mMaxValue;
|
||||
}
|
||||
|
||||
public void setMaxValue(int maxValue) {
|
||||
this.mMaxValue = maxValue;
|
||||
}
|
||||
|
||||
public interface OnValueChangeListener {
|
||||
void onChange(int value, int previousValue);
|
||||
}
|
||||
|
||||
public interface OnFormatValueCallback {
|
||||
String onFormatValue(int value);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final TextView text;
|
||||
final TextView label;
|
||||
final Button increment;
|
||||
final Button decrement;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
text = root.findViewById(R.id.text);
|
||||
label = root.findViewById(R.id.label);
|
||||
increment = root.findViewById(R.id.increment);
|
||||
decrement = root.findViewById(R.id.decrement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class DashboardFragment extends MCFragment {
|
||||
private DashboardViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private DashboardRecyclerViewAdapter mAdapter;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(DashboardViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setupRecyclerView(mHolder.list);
|
||||
|
||||
getMonsterRepository()
|
||||
.getMonsters()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(monsters -> mViewModel.setMonsters(monsters));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
int columnCount = Math.max(1, getResources().getConfiguration().screenWidthDp / 396);
|
||||
Logger.logWTF(String.format(Locale.US, "Setting column count to %d", columnCount));
|
||||
Context context = requireContext();
|
||||
GridLayoutManager layoutManager = new GridLayoutManager(context, columnCount);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<Monster>> monsterData = mViewModel.getMonsters();
|
||||
mAdapter = new DashboardRecyclerViewAdapter(monster -> {
|
||||
if (monster != null) {
|
||||
navigateToMonsterDetail(monster);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to MonsterDetailFragment with a null monster");
|
||||
}
|
||||
});
|
||||
if (monsterData != null) {
|
||||
monsterData.observe(getViewLifecycleOwner(), monsters -> mAdapter.submitList(monsters));
|
||||
}
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
private void navigateToMonsterDetail(Monster monster) {
|
||||
NavDirections action = DashboardFragmentDirections.actionNavigationDashboardToNavigationMonster(monster.id.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final RecyclerView list;
|
||||
|
||||
ViewHolder(View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import android.text.Html;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.databinding.CardMonsterBinding;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class DashboardRecyclerViewAdapter extends ListAdapter<Monster, DashboardRecyclerViewAdapter.ViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Monster> DIFF_CALLBACK = new DiffUtil.ItemCallback<Monster>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return oldItem.id.equals(newItem.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Monster oldItem, @NonNull Monster newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
protected DashboardRecyclerViewAdapter(ItemCallback onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(CardMonsterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Logger.logUnimplementedMethod();
|
||||
Monster monster = getItem(position);
|
||||
holder.monster = monster;
|
||||
holder.name.setText(monster.name);
|
||||
holder.meta.setText(monster.getMeta());
|
||||
holder.strengthAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.strengthSavingThrowAdvantage));
|
||||
holder.strengthModifier.setText(Helpers.getModifierString(monster.getStrengthModifier()));
|
||||
holder.strengthName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.STRENGTH));
|
||||
holder.strengthProficiency.setText(Helpers.getProficiencyAbbreviation(monster.strengthSavingThrowProficiency));
|
||||
|
||||
holder.dexterityAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.dexteritySavingThrowAdvantage));
|
||||
holder.dexterityModifier.setText(Helpers.getModifierString(monster.getDexterityModifier()));
|
||||
holder.dexterityName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.DEXTERITY));
|
||||
holder.dexterityProficiency.setText(Helpers.getProficiencyAbbreviation(monster.dexteritySavingThrowProficiency));
|
||||
|
||||
holder.constitutionAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.constitutionSavingThrowAdvantage));
|
||||
holder.constitutionModifier.setText(Helpers.getModifierString(monster.getConstitutionModifier()));
|
||||
holder.constitutionName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.CONSTITUTION));
|
||||
holder.constitutionProficiency.setText(Helpers.getProficiencyAbbreviation(monster.constitutionSavingThrowProficiency));
|
||||
|
||||
holder.intelligenceAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.intelligenceSavingThrowAdvantage));
|
||||
holder.intelligenceModifier.setText(Helpers.getModifierString(monster.getIntelligenceModifier()));
|
||||
holder.intelligenceName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.INTELLIGENCE));
|
||||
holder.intelligenceProficiency.setText(Helpers.getProficiencyAbbreviation(monster.intelligenceSavingThrowProficiency));
|
||||
|
||||
holder.wisdomAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.wisdomSavingThrowAdvantage));
|
||||
holder.wisdomModifier.setText(Helpers.getModifierString(monster.getWisdomModifier()));
|
||||
holder.wisdomName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.WISDOM));
|
||||
holder.wisdomProficiency.setText(Helpers.getProficiencyAbbreviation(monster.wisdomSavingThrowProficiency));
|
||||
|
||||
holder.charismaAdvantage.setText(Helpers.getAdvantageAbbreviation(monster.charismaSavingThrowAdvantage));
|
||||
holder.charismaModifier.setText(Helpers.getModifierString(monster.getCharismaModifier()));
|
||||
holder.charismaName.setText(Helpers.getAbilityScoreAbbreviation(AbilityScore.CHARISMA));
|
||||
holder.charismaProficiency.setText(Helpers.getProficiencyAbbreviation(monster.charismaSavingThrowProficiency));
|
||||
|
||||
holder.armorClass.setText(String.valueOf(monster.getArmorClassValue()));
|
||||
holder.hitPoints.setText(String.valueOf(monster.getHitPointsValue()));
|
||||
holder.challengeRating.setText(holder.challengeRating.getResources().getString(R.string.label_challenge_rating_with_value, Helpers.getChallengeRatingAbbreviation(monster.challengeRating)));
|
||||
|
||||
int numActions = monster.actions.size();
|
||||
if (numActions > 0) {
|
||||
holder.action1Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(0);
|
||||
holder.action1Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action1Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action1Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (numActions > 1) {
|
||||
holder.action2Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(1);
|
||||
holder.action2Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action2Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action2Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (numActions > 2) {
|
||||
holder.action3Group.setVisibility(View.VISIBLE);
|
||||
Trait action = monster.actions.get(2);
|
||||
holder.action3Description.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.description)));
|
||||
holder.action3Name.setText(Html.fromHtml(CommonMarkHelper.toHtml(action.name)));
|
||||
} else {
|
||||
holder.action3Group.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.monster);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Monster monster);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView name;
|
||||
public final TextView meta;
|
||||
public final View action1Group;
|
||||
public final TextView action1Name;
|
||||
public final TextView action1Description;
|
||||
public final View action2Group;
|
||||
public final TextView action2Name;
|
||||
public final TextView action2Description;
|
||||
public final View action3Group;
|
||||
public final TextView action3Name;
|
||||
public final TextView action3Description;
|
||||
public final TextView strengthName;
|
||||
public final TextView strengthModifier;
|
||||
public final TextView strengthProficiency;
|
||||
public final TextView strengthAdvantage;
|
||||
public final TextView dexterityName;
|
||||
public final TextView dexterityModifier;
|
||||
public final TextView dexterityProficiency;
|
||||
public final TextView dexterityAdvantage;
|
||||
public final TextView constitutionName;
|
||||
public final TextView constitutionModifier;
|
||||
public final TextView constitutionProficiency;
|
||||
public final TextView constitutionAdvantage;
|
||||
public final TextView intelligenceName;
|
||||
public final TextView intelligenceModifier;
|
||||
public final TextView intelligenceProficiency;
|
||||
public final TextView intelligenceAdvantage;
|
||||
public final TextView wisdomName;
|
||||
public final TextView wisdomModifier;
|
||||
public final TextView wisdomProficiency;
|
||||
public final TextView wisdomAdvantage;
|
||||
public final TextView charismaName;
|
||||
public final TextView charismaModifier;
|
||||
public final TextView charismaProficiency;
|
||||
public final TextView charismaAdvantage;
|
||||
public final TextView armorClass;
|
||||
public final TextView hitPoints;
|
||||
public final TextView challengeRating;
|
||||
public Monster monster;
|
||||
|
||||
public ViewHolder(@NonNull CardMonsterBinding binding) {
|
||||
super(binding.getRoot());
|
||||
name = binding.name;
|
||||
meta = binding.meta;
|
||||
action1Group = binding.action1.getRoot();
|
||||
action1Name = binding.action1.name;
|
||||
action1Description = binding.action1.description;
|
||||
action2Group = binding.action2.getRoot();
|
||||
action2Name = binding.action2.name;
|
||||
action2Description = binding.action2.description;
|
||||
action3Group = binding.action3.getRoot();
|
||||
action3Name = binding.action3.name;
|
||||
action3Description = binding.action3.description;
|
||||
strengthName = binding.strength.name;
|
||||
strengthModifier = binding.strength.modifier;
|
||||
strengthProficiency = binding.strength.proficiency;
|
||||
strengthAdvantage = binding.strength.advantage;
|
||||
dexterityName = binding.dexterity.name;
|
||||
dexterityModifier = binding.dexterity.modifier;
|
||||
dexterityProficiency = binding.dexterity.proficiency;
|
||||
dexterityAdvantage = binding.dexterity.advantage;
|
||||
constitutionName = binding.constitution.name;
|
||||
constitutionModifier = binding.constitution.modifier;
|
||||
constitutionProficiency = binding.constitution.proficiency;
|
||||
constitutionAdvantage = binding.constitution.advantage;
|
||||
intelligenceName = binding.intelligence.name;
|
||||
intelligenceModifier = binding.intelligence.modifier;
|
||||
intelligenceProficiency = binding.intelligence.proficiency;
|
||||
intelligenceAdvantage = binding.intelligence.advantage;
|
||||
wisdomName = binding.wisdom.name;
|
||||
wisdomModifier = binding.wisdom.modifier;
|
||||
wisdomProficiency = binding.wisdom.proficiency;
|
||||
wisdomAdvantage = binding.wisdom.advantage;
|
||||
charismaName = binding.charisma.name;
|
||||
charismaModifier = binding.charisma.modifier;
|
||||
charismaProficiency = binding.charisma.proficiency;
|
||||
charismaAdvantage = binding.charisma.advantage;
|
||||
armorClass = binding.armorClass.value;
|
||||
hitPoints = binding.hitPoints.value;
|
||||
challengeRating = binding.challengeRating;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Helpers {
|
||||
@NonNull
|
||||
public static String getModifierString(int value) {
|
||||
return String.format(Locale.getDefault(), "%+d", value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getAbilityScoreAbbreviation(@NonNull AbilityScore abilityScore) {
|
||||
switch (abilityScore) {
|
||||
case STRENGTH:
|
||||
return "S";
|
||||
case DEXTERITY:
|
||||
return "D";
|
||||
case CONSTITUTION:
|
||||
return "C";
|
||||
case INTELLIGENCE:
|
||||
return "I";
|
||||
case WISDOM:
|
||||
return "W";
|
||||
case CHARISMA:
|
||||
return "Ch";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for AbilityScore value %s", abilityScore));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getChallengeRatingAbbreviation(@NonNull ChallengeRating challengeRating) {
|
||||
Logger.logUnimplementedMethod();
|
||||
switch (challengeRating) {
|
||||
case CUSTOM:
|
||||
return "*";
|
||||
case ZERO:
|
||||
return "0";
|
||||
case ONE_EIGHTH:
|
||||
return "1/8";
|
||||
case ONE_QUARTER:
|
||||
return "1/4";
|
||||
case ONE_HALF:
|
||||
return "1/2";
|
||||
case ONE:
|
||||
return "1";
|
||||
case TWO:
|
||||
return "2";
|
||||
case THREE:
|
||||
return "3";
|
||||
case FOUR:
|
||||
return "4";
|
||||
case FIVE:
|
||||
return "5";
|
||||
case SIX:
|
||||
return "6";
|
||||
case SEVEN:
|
||||
return "7";
|
||||
case EIGHT:
|
||||
return "8";
|
||||
case NINE:
|
||||
return "9";
|
||||
case TEN:
|
||||
return "10";
|
||||
case ELEVEN:
|
||||
return "11";
|
||||
case TWELVE:
|
||||
return "12";
|
||||
case THIRTEEN:
|
||||
return "13";
|
||||
case FOURTEEN:
|
||||
return "14";
|
||||
case FIFTEEN:
|
||||
return "15";
|
||||
case SIXTEEN:
|
||||
return "16";
|
||||
case SEVENTEEN:
|
||||
return "17";
|
||||
case EIGHTEEN:
|
||||
return "18";
|
||||
case NINETEEN:
|
||||
return "19";
|
||||
case TWENTY:
|
||||
return "20";
|
||||
case TWENTY_ONE:
|
||||
return "21";
|
||||
case TWENTY_TWO:
|
||||
return "22";
|
||||
case TWENTY_THREE:
|
||||
return "23";
|
||||
case TWENTY_FOUR:
|
||||
return "24";
|
||||
case TWENTY_FIVE:
|
||||
return "25";
|
||||
case TWENTY_SIX:
|
||||
return "26";
|
||||
case TWENTY_SEVEN:
|
||||
return "27";
|
||||
case TWENTY_EIGHT:
|
||||
return "28";
|
||||
case TWENTY_NINE:
|
||||
return "29";
|
||||
case THIRTY:
|
||||
return "30";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for ChallengeRating value %s", challengeRating));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getProficiencyAbbreviation(@NonNull ProficiencyType proficiency) {
|
||||
switch (proficiency) {
|
||||
case NONE:
|
||||
return "";
|
||||
case EXPERTISE:
|
||||
return "E";
|
||||
case PROFICIENT:
|
||||
return "P";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for ProficiencyType value %s", proficiency));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getAdvantageAbbreviation(@NonNull AdvantageType advantage) {
|
||||
switch (advantage) {
|
||||
case NONE:
|
||||
return "";
|
||||
case ADVANTAGE:
|
||||
return "A";
|
||||
case DISADVANTAGE:
|
||||
return "D";
|
||||
default:
|
||||
Logger.logUnimplementedFeature(String.format("Get an abbreviation for AdvantageType value %s", advantage));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.majinnaibu.monstercards.ui.dashboard;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DashboardViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<Monster>> mMonsters;
|
||||
|
||||
public DashboardViewModel() {
|
||||
mMonsters = new MutableLiveData<>(new ArrayList<>());
|
||||
}
|
||||
|
||||
public LiveData<List<Monster>> getMonsters() {
|
||||
return mMonsters;
|
||||
}
|
||||
|
||||
public void setMonsters(List<Monster> monsters) {
|
||||
mMonsters.setValue(monsters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class EditAbilityScoresFragment extends MCFragment {
|
||||
private final String ABILITY_SCORE_FORMAT = "%d (%+d)";
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private int getModifier(int value) {
|
||||
return value / 2 - 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_ability_scores, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), value -> mHolder.strength.setValue(value));
|
||||
mHolder.strength.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setStrength(newValue));
|
||||
mHolder.strength.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), value -> mHolder.dexterity.setValue(value));
|
||||
mHolder.dexterity.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setDexterity(newValue));
|
||||
mHolder.dexterity.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), value -> mHolder.constitution.setValue(value));
|
||||
mHolder.constitution.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setConstitution(newValue));
|
||||
mHolder.constitution.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), value -> mHolder.intelligence.setValue(value));
|
||||
mHolder.intelligence.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setIntelligence(newValue));
|
||||
mHolder.intelligence.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), value -> mHolder.wisdom.setValue(value));
|
||||
mHolder.wisdom.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWisdom(newValue));
|
||||
mHolder.wisdom.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), value -> mHolder.charisma.setValue(value));
|
||||
mHolder.charisma.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setCharisma(newValue));
|
||||
mHolder.charisma.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), ABILITY_SCORE_FORMAT, value, getModifier(value)));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final Stepper strength;
|
||||
final Stepper dexterity;
|
||||
final Stepper constitution;
|
||||
final Stepper intelligence;
|
||||
final Stepper wisdom;
|
||||
final Stepper charisma;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ArmorType;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditArmorFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_armor, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.armorType.setAdapter(new ArrayAdapter<ArmorType>(requireContext(), R.layout.dropdown_list_item, ArmorType.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ArmorType item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ArmorType item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.armorType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ArmorType selectedItem = (ArmorType) parent.getItemAtPosition(position);
|
||||
mViewModel.setArmorType(selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mViewModel.setArmorType(ArmorType.NONE);
|
||||
}
|
||||
});
|
||||
mHolder.armorType.setSelection(ArrayHelper.indexOf(ArmorType.values(), mViewModel.getArmorType().getValue()));
|
||||
|
||||
mHolder.naturalArmorBonus.setValue(mViewModel.getNaturalArmorBonusUnboxed());
|
||||
mHolder.naturalArmorBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setNaturalArmorBonus(newValue));
|
||||
|
||||
mHolder.hasShield.setChecked(mViewModel.getHasShieldValueAsBoolean());
|
||||
mHolder.hasShield.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasShield(isChecked));
|
||||
|
||||
mHolder.shieldBonus.setValue(mViewModel.getShieldBonusUnboxed());
|
||||
mHolder.shieldBonus.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setShieldBonus(newValue));
|
||||
|
||||
mHolder.customArmor.setText(mViewModel.getCustomArmor().getValue());
|
||||
mHolder.customArmor.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomArmor(s.toString()))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private final Spinner armorType;
|
||||
private final Stepper naturalArmorBonus;
|
||||
private final SwitchCompat hasShield;
|
||||
private final Stepper shieldBonus;
|
||||
private final EditText customArmor;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
armorType = root.findViewById(R.id.armorType);
|
||||
naturalArmorBonus = root.findViewById(R.id.naturalArmorBonus);
|
||||
hasShield = root.findViewById(R.id.hasShield);
|
||||
shieldBonus = root.findViewById(R.id.shieldBonus);
|
||||
customArmor = root.findViewById(R.id.customArmor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditBasicInfoFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_basic_info, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.size.setText(mViewModel.getSize().getValue());
|
||||
mHolder.size.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSize(s.toString())));
|
||||
|
||||
mHolder.type.setText(mViewModel.getType().getValue());
|
||||
mHolder.type.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setType(s.toString())));
|
||||
|
||||
mHolder.subtype.setText(mViewModel.getSubtype().getValue());
|
||||
mHolder.subtype.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setSubtype(s.toString())));
|
||||
|
||||
mHolder.alignment.setText(mViewModel.getAlignment().getValue());
|
||||
mHolder.alignment.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setAlignment(s.toString())));
|
||||
|
||||
mHolder.customHitPoints.setText(mViewModel.getCustomHitPoints().getValue());
|
||||
mHolder.customHitPoints.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomHitPoints(s.toString()))));
|
||||
|
||||
mHolder.hitDice.setValue(mViewModel.getHitDiceUnboxed());
|
||||
mHolder.hitDice.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setHitDice(newValue));
|
||||
|
||||
mHolder.hasCustomHitPoints.setChecked(mViewModel.getHasCustomHitPointsValueAsBoolean());
|
||||
mHolder.hasCustomHitPoints.setOnCheckedChangeListener((button, isChecked) -> mViewModel.setHasCustomHitPoints(isChecked));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
private final EditText name;
|
||||
private final EditText size;
|
||||
private final EditText type;
|
||||
private final EditText subtype;
|
||||
private final EditText alignment;
|
||||
private final EditText customHitPoints;
|
||||
private final Stepper hitDice;
|
||||
private final SwitchMaterial hasCustomHitPoints;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
size = root.findViewById(R.id.size);
|
||||
type = root.findViewById(R.id.type);
|
||||
subtype = root.findViewById(R.id.subtype);
|
||||
alignment = root.findViewById(R.id.alignment);
|
||||
customHitPoints = root.findViewById(R.id.customHitPoints);
|
||||
hitDice = root.findViewById(R.id.hitDice);
|
||||
hasCustomHitPoints = root.findViewById(R.id.hasCustomHitPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.ChallengeRating;
|
||||
import com.majinnaibu.monstercards.helpers.ArrayHelper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditChallengeRatingFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_challenge_rating, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.challengeRating.setAdapter(new ArrayAdapter<ChallengeRating>(requireContext(), R.layout.dropdown_list_item, ChallengeRating.values()) {
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ChallengeRating item = getItem(position);
|
||||
TextView view = (TextView) super.getView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ChallengeRating item = getItem(position);
|
||||
TextView view = (TextView) super.getDropDownView(position, convertView, parent);
|
||||
view.setText(item.displayName);
|
||||
return view;
|
||||
}
|
||||
});
|
||||
mHolder.challengeRating.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
ChallengeRating selectedItem = (ChallengeRating) parent.getItemAtPosition(position);
|
||||
mViewModel.setChallengeRating(selectedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
mViewModel.setChallengeRating(ChallengeRating.CUSTOM);
|
||||
}
|
||||
});
|
||||
mHolder.challengeRating.setSelection(ArrayHelper.indexOf(ChallengeRating.values(), mViewModel.getChallengeRating().getValue()));
|
||||
|
||||
mHolder.customChallengeRatingDescription.setText(mViewModel.getCustomChallengeRatingDescription().getValue());
|
||||
mHolder.customChallengeRatingDescription.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomChallengeRatingDescription(s.toString()))));
|
||||
|
||||
mHolder.customProficiencyBonus.setText(mViewModel.getCustomProficiencyBonusValueAsString());
|
||||
mHolder.customProficiencyBonus.addTextChangedListener((new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomProficiencyBonus(s.toString()))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final Spinner challengeRating;
|
||||
final EditText customChallengeRatingDescription;
|
||||
final EditText customProficiencyBonus;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
challengeRating = root.findViewById(R.id.challengeRating);
|
||||
customChallengeRatingDescription = root.findViewById(R.id.customChallengeRatingDescription);
|
||||
customProficiencyBonus = root.findViewById(R.id.customProficiencyBonus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditLanguageFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditLanguageViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private Language mOldLanguage;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditLanguageViewModel.class);
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
EditLanguageFragmentArgs args = EditLanguageFragmentArgs.fromBundle(arguments);
|
||||
mOldLanguage = new Language(args.getName(), args.getCanSpeak());
|
||||
mViewModel.copyFromLanguage(mOldLanguage);
|
||||
} else {
|
||||
Logger.logWTF("EditLanguageFragment needs arguments");
|
||||
mOldLanguage = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_language, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.canSpeak.setChecked(mViewModel.getCanSpeakValue());
|
||||
mHolder.canSpeak.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setCanSpeak(isChecked));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceLanguage(mOldLanguage, mViewModel.getLanguage().getValue());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText name;
|
||||
SwitchCompat canSpeak;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
canSpeak = root.findViewById(R.id.canSpeak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditLanguageViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<Boolean> mCanSpeak;
|
||||
private final ChangeTrackedLiveData<Language> mLanguage;
|
||||
|
||||
public EditLanguageViewModel() {
|
||||
super();
|
||||
mName = new ChangeTrackedLiveData<>("New Language", this::makeDirty);
|
||||
mCanSpeak = new ChangeTrackedLiveData<>(true, this::makeDirty);
|
||||
mLanguage = new ChangeTrackedLiveData<>(makeLanguage(), this::makeDirty);
|
||||
}
|
||||
|
||||
public void copyFromLanguage(@NonNull Language language) {
|
||||
mName.resetValue(language.getName());
|
||||
mCanSpeak.resetValue(language.getSpeaks());
|
||||
makeClean();
|
||||
}
|
||||
|
||||
public LiveData<Language> getLanguage() {
|
||||
return mLanguage;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName.setValue(name);
|
||||
mLanguage.setValue(makeLanguage());
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getCanSpeak() {
|
||||
return mCanSpeak;
|
||||
}
|
||||
|
||||
public void setCanSpeak(boolean canSpeak) {
|
||||
mCanSpeak.setValue(canSpeak);
|
||||
mLanguage.setValue(makeLanguage());
|
||||
}
|
||||
|
||||
public boolean getCanSpeakValue(boolean defaultIfNull) {
|
||||
Boolean boxedValue = mCanSpeak.getValue();
|
||||
if (boxedValue == null) {
|
||||
return defaultIfNull;
|
||||
}
|
||||
return boxedValue;
|
||||
}
|
||||
|
||||
public boolean getCanSpeakValue() {
|
||||
return getCanSpeakValue(false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Language makeLanguage() {
|
||||
Boolean boxedValue = mCanSpeak.getValue();
|
||||
boolean canSpeak = boxedValue != null && boxedValue;
|
||||
return new Language(mName.getValue(), canSpeak);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditLanguagesFragment extends MCFragment {
|
||||
// TODO: Make the swipe to delete not happen for the header
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private void navigateToEditLanguage(@NonNull Language language) {
|
||||
NavDirections action = EditLanguagesFragmentDirections.actionEditLanguagesFragmentToEditLanguageFragment(language.getName(), language.getSpeaks());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_languages_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddLanguageButton(mHolder.addLanguage);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> {
|
||||
EditLanguagesRecyclerViewAdapter adapter = new EditLanguagesRecyclerViewAdapter(
|
||||
mViewModel.getLanguagesArray(),
|
||||
language -> {
|
||||
if (language != null) {
|
||||
navigateToEditLanguage(language);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditSkill with a null skill");
|
||||
}
|
||||
},
|
||||
mViewModel.getTelepathyRangeUnboxed(),
|
||||
(value, previousValue) -> mViewModel.setTelepathyRange(value),
|
||||
mViewModel.getUnderstandsButDescription().getValue(),
|
||||
new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setUnderstandsButDescription(s.toString())));
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> {
|
||||
if (position > 0) {
|
||||
mViewModel.removeLanguage(position - 1);
|
||||
}
|
||||
}, null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddLanguageButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Language newLanguage = mViewModel.addNewLanguage();
|
||||
navigateToEditLanguage(newLanguage);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addLanguage;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
this.list = root.findViewById(R.id.list);
|
||||
this.addLanguage = root.findViewById(R.id.add_language);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListHeaderBinding;
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditLanguagesListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Language;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class EditLanguagesRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private final List<Language> mValues;
|
||||
private final ItemCallback mOnClick;
|
||||
private final int mTelepathyRange;
|
||||
private final String mUnderstandsBut;
|
||||
private final Stepper.OnValueChangeListener mOnTelepathyRangeChanged;
|
||||
private final TextWatcher mOnUnderstandsButChanged;
|
||||
|
||||
private final int HEADER_VIEW_TYPE = 1;
|
||||
private final int ITEM_VIEW_TYPE = 2;
|
||||
private final String DISTANCE_IN_FEET_FORMAT = "%d ft.";
|
||||
|
||||
public EditLanguagesRecyclerViewAdapter(List<Language> items, ItemCallback onClick, int telepathyRange, Stepper.OnValueChangeListener telepathyRangeChangedListener, String understandsBut, TextWatcher understandsButChangedListener) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
mTelepathyRange = telepathyRange;
|
||||
mOnTelepathyRangeChanged = telepathyRangeChangedListener;
|
||||
mUnderstandsBut = understandsBut;
|
||||
mOnUnderstandsButChanged = understandsButChangedListener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == HEADER_VIEW_TYPE) {
|
||||
return new HeaderViewHolder(FragmentEditLanguagesListHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
return new ItemViewHolder(FragmentEditLanguagesListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) {
|
||||
if (holder instanceof HeaderViewHolder) {
|
||||
HeaderViewHolder headerViewHolder = (HeaderViewHolder) holder;
|
||||
headerViewHolder.telepathy.setOnFormatValueCallback(value -> String.format(Locale.getDefault(), DISTANCE_IN_FEET_FORMAT, value));
|
||||
headerViewHolder.telepathy.setValue(mTelepathyRange);
|
||||
headerViewHolder.telepathy.setOnValueChangeListener(mOnTelepathyRangeChanged);
|
||||
headerViewHolder.understandsBut.setText(mUnderstandsBut);
|
||||
headerViewHolder.understandsBut.addTextChangedListener(mOnUnderstandsButChanged);
|
||||
} else if (holder instanceof ItemViewHolder) {
|
||||
ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
|
||||
itemViewHolder.mItem = mValues.get(position - 1);
|
||||
itemViewHolder.mContentView.setText(itemViewHolder.mItem.getName());
|
||||
itemViewHolder.itemView.setOnClickListener(view -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(itemViewHolder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size() + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) {
|
||||
return HEADER_VIEW_TYPE;
|
||||
}
|
||||
return ITEM_VIEW_TYPE;
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Language language);
|
||||
}
|
||||
|
||||
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
public final Stepper telepathy;
|
||||
public final EditText understandsBut;
|
||||
|
||||
public HeaderViewHolder(@NonNull FragmentEditLanguagesListHeaderBinding binding) {
|
||||
super(binding.getRoot());
|
||||
telepathy = binding.telepathy;
|
||||
understandsBut = binding.understandsBut;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Language mItem;
|
||||
|
||||
public ItemViewHolder(@NonNull FragmentEditLanguagesListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.monster.MonsterDetailFragmentArgs;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class EditMonsterFragment extends MCFragment {
|
||||
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
|
||||
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
|
||||
View root = inflater.inflate(R.layout.fragment_edit_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
setTitle(getString(R.string.title_editMonster_fmt, getString(R.string.default_monster_name)));
|
||||
|
||||
// TODO: Show a loading spinner until we have the monster loaded.
|
||||
if (mViewModel.hasError() || !mViewModel.hasLoaded() || !Objects.equals(mViewModel.getMonsterId().getValue(), monsterId)) {
|
||||
repository.getMonster(monsterId).toObservable()
|
||||
.firstOrError()
|
||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||
mViewModel.setHasLoaded(true);
|
||||
mViewModel.setHasError(false);
|
||||
mViewModel.copyFromMonster(monster);
|
||||
setTitle(getString(R.string.title_editMonster_fmt, monster.name));
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
// TODO: Show an error state.
|
||||
Logger.logError(e);
|
||||
mViewModel.setHasError(true);
|
||||
mViewModel.setErrorMessage(e.toString());
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mHolder.basicInfoButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditBasicInfoFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.armorButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditArmorFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.speedButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSpeedFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.abilityScoresButton.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditAbilityScoresFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.savingThrows.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSavingThrowsFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.challengeRating.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditChallengeRatingFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.skills.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditSkillsFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.senses.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.SENSE);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.conditionImmunities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.CONDITION_IMMUNITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageImmunities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_IMMUNITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageResistances.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_RESISTANCE);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.damageVulnerabilities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditStringsFragment(StringType.DAMAGE_VULNERABILITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.languages.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditLanguagesFragment();
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.abilities.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.ABILITY);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.actions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.lairActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.LAIR_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.legendaryActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.LEGENDARY_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.reactions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.REACTIONS);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
mHolder.regionalActions.setOnClickListener(v -> {
|
||||
NavDirections action = EditMonsterFragmentDirections.actionEditMonsterFragmentToEditTraitListFragment(TraitType.REGIONAL_ACTION);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
});
|
||||
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
View view = getView();
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(requireContext()).create();
|
||||
alertDialog.setTitle("Unsaved Changes");
|
||||
alertDialog.setMessage("Do you want to save your changes?");
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "Save", (dialog, id) -> {
|
||||
// Save the monster. Navigate up if the save is successful. Show a SnackBar if there was an error.
|
||||
getMonsterRepository().saveMonster(mViewModel.buildMonster())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
assert view != null;
|
||||
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Discard", (dialog, id) -> {
|
||||
// Navigate up ignoring unsaved changes.
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
});
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Cancel", (dialog, id) -> {
|
||||
// Do nothing.
|
||||
});
|
||||
alertDialog.show();
|
||||
} else {
|
||||
// No changes so we can safely leave.
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
TextView basicInfoButton;
|
||||
TextView armorButton;
|
||||
TextView speedButton;
|
||||
TextView abilityScoresButton;
|
||||
TextView savingThrows;
|
||||
TextView skills;
|
||||
TextView conditionImmunities;
|
||||
TextView damageImmunities;
|
||||
TextView damageResistances;
|
||||
TextView damageVulnerabilities;
|
||||
TextView senses;
|
||||
TextView languages;
|
||||
TextView challengeRating;
|
||||
TextView abilities;
|
||||
TextView actions;
|
||||
TextView reactions;
|
||||
TextView legendaryActions;
|
||||
TextView lairActions;
|
||||
TextView regionalActions;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
basicInfoButton = root.findViewById(R.id.basicInfo);
|
||||
armorButton = root.findViewById(R.id.armor);
|
||||
speedButton = root.findViewById(R.id.speed);
|
||||
abilityScoresButton = root.findViewById(R.id.abilityScores);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challengeRating = root.findViewById(R.id.challengeRating);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
regionalActions = root.findViewById(R.id.regionalActions);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class EditSavingThrowsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mViewHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_saving_throws, container, false);
|
||||
mViewHolder = new ViewHolder(root);
|
||||
|
||||
mViewHolder.strengthProficiency.setValue(mViewModel.getStrengthProficiency().getValue());
|
||||
mViewHolder.strengthProficiency.setOnValueChangedListener(value -> mViewModel.setStrengthProficiency(value));
|
||||
mViewHolder.strengthAdvantage.setValue(mViewModel.getStrengthAdvantage().getValue());
|
||||
mViewHolder.strengthAdvantage.setOnValueChangedListener(value -> mViewModel.setStrengthAdvantage(value));
|
||||
|
||||
mViewHolder.dexterityProficiency.setValue(mViewModel.getDexterityProficiency().getValue());
|
||||
mViewHolder.dexterityProficiency.setOnValueChangedListener(value -> mViewModel.setDexterityProficiency(value));
|
||||
mViewHolder.dexterityAdvantage.setValue(mViewModel.getDexterityAdvantage().getValue());
|
||||
mViewHolder.dexterityAdvantage.setOnValueChangedListener(value -> mViewModel.setDexterityAdvantage(value));
|
||||
|
||||
mViewHolder.constitutionProficiency.setValue(mViewModel.getConstitutionProficiency().getValue());
|
||||
mViewHolder.constitutionProficiency.setOnValueChangedListener(value -> mViewModel.setConstitutionProficiency(value));
|
||||
mViewHolder.constitutionAdvantage.setValue(mViewModel.getConstitutionAdvantage().getValue());
|
||||
mViewHolder.constitutionAdvantage.setOnValueChangedListener(value -> mViewModel.setConstitutionAdvantage(value));
|
||||
|
||||
mViewHolder.intelligenceProficiency.setValue(mViewModel.getIntelligenceProficiency().getValue());
|
||||
mViewHolder.intelligenceProficiency.setOnValueChangedListener(value -> mViewModel.setIntelligenceProficiency(value));
|
||||
mViewHolder.intelligenceAdvantage.setValue(mViewModel.getIntelligenceAdvantage().getValue());
|
||||
mViewHolder.intelligenceAdvantage.setOnValueChangedListener(value -> mViewModel.setIntelligenceAdvantage(value));
|
||||
|
||||
mViewHolder.wisdomProficiency.setValue(mViewModel.getWisdomProficiency().getValue());
|
||||
mViewHolder.wisdomProficiency.setOnValueChangedListener(value -> mViewModel.setWisdomProficiency(value));
|
||||
mViewHolder.wisdomAdvantage.setValue(mViewModel.getWisdomAdvantage().getValue());
|
||||
mViewHolder.wisdomAdvantage.setOnValueChangedListener(value -> mViewModel.setWisdomAdvantage(value));
|
||||
|
||||
mViewHolder.charismaProficiency.setValue(mViewModel.getCharismaProficiency().getValue());
|
||||
mViewHolder.charismaProficiency.setOnValueChangedListener(value -> mViewModel.setCharismaProficiency(value));
|
||||
mViewHolder.charismaAdvantage.setValue(mViewModel.getCharismaAdvantage().getValue());
|
||||
mViewHolder.charismaAdvantage.setOnValueChangedListener(value -> mViewModel.setCharismaAdvantage(value));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
AdvantagePicker strengthAdvantage;
|
||||
ProficiencyPicker strengthProficiency;
|
||||
AdvantagePicker dexterityAdvantage;
|
||||
ProficiencyPicker dexterityProficiency;
|
||||
AdvantagePicker constitutionAdvantage;
|
||||
ProficiencyPicker constitutionProficiency;
|
||||
AdvantagePicker intelligenceAdvantage;
|
||||
ProficiencyPicker intelligenceProficiency;
|
||||
AdvantagePicker wisdomAdvantage;
|
||||
ProficiencyPicker wisdomProficiency;
|
||||
AdvantagePicker charismaAdvantage;
|
||||
ProficiencyPicker charismaProficiency;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
strengthAdvantage = root.findViewById(R.id.strengthAdvantage);
|
||||
strengthProficiency = root.findViewById(R.id.strengthProficiency);
|
||||
dexterityAdvantage = root.findViewById(R.id.dexterityAdvantage);
|
||||
dexterityProficiency = root.findViewById(R.id.dexterityProficiency);
|
||||
constitutionAdvantage = root.findViewById(R.id.constitutionAdvantage);
|
||||
constitutionProficiency = root.findViewById(R.id.constitutionProficiency);
|
||||
intelligenceAdvantage = root.findViewById(R.id.intelligenceAdvantage);
|
||||
intelligenceProficiency = root.findViewById(R.id.intelligenceProficiency);
|
||||
wisdomAdvantage = root.findViewById(R.id.wisdomAdvantage);
|
||||
wisdomProficiency = root.findViewById(R.id.wisdomProficiency);
|
||||
charismaAdvantage = root.findViewById(R.id.charismaAdvantage);
|
||||
charismaProficiency = root.findViewById(R.id.charismaProficiency);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.components.AbilityScorePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.AdvantagePicker;
|
||||
import com.majinnaibu.monstercards.ui.components.ProficiencyPicker;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditSkillFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditSkillViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private Skill mOldSkill;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditSkillViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditSkillFragmentArgs args = EditSkillFragmentArgs.fromBundle(getArguments());
|
||||
mOldSkill = new Skill(args.getName(), args.getAbilityScore(), args.getAdvantage(), args.getProficiency());
|
||||
mViewModel.copyFromSkill(mOldSkill);
|
||||
} else {
|
||||
Logger.logWTF("EditSkillFragment needs arguments.");
|
||||
mOldSkill = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_skill, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.abilityScore.setValue(mViewModel.getAbilityScore().getValue());
|
||||
mHolder.abilityScore.setOnValueChangedListener(value -> mViewModel.setAbilityScore(value));
|
||||
|
||||
mHolder.advantage.setValue(mViewModel.getAdvantage().getValue());
|
||||
mHolder.advantage.setOnValueChangedListener(value -> mViewModel.setAdvantage(value));
|
||||
|
||||
mHolder.proficiency.setValue(mViewModel.getProficiency().getValue());
|
||||
mHolder.proficiency.setOnValueChangedListener(value -> mViewModel.setProficiency(value));
|
||||
|
||||
mHolder.name.setText(mViewModel.getName().getValue());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceSkill(mViewModel.getSkill().getValue(), mOldSkill);
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
AbilityScorePicker abilityScore;
|
||||
AdvantagePicker advantage;
|
||||
ProficiencyPicker proficiency;
|
||||
EditText name;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
abilityScore = root.findViewById(R.id.abilityScore);
|
||||
advantage = root.findViewById(R.id.advantage);
|
||||
proficiency = root.findViewById(R.id.proficiency);
|
||||
name = root.findViewById(R.id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.data.enums.AbilityScore;
|
||||
import com.majinnaibu.monstercards.data.enums.AdvantageType;
|
||||
import com.majinnaibu.monstercards.data.enums.ProficiencyType;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditSkillViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<AbilityScore> mAbilityScore;
|
||||
private final ChangeTrackedLiveData<AdvantageType> mAdvantageType;
|
||||
private final ChangeTrackedLiveData<ProficiencyType> mProficiencyType;
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<Skill> mSkill;
|
||||
|
||||
public EditSkillViewModel() {
|
||||
super();
|
||||
mAbilityScore = new ChangeTrackedLiveData<>(AbilityScore.STRENGTH, this::makeDirty);
|
||||
mAdvantageType = new ChangeTrackedLiveData<>(AdvantageType.NONE, this::makeDirty);
|
||||
mProficiencyType = new ChangeTrackedLiveData<>(ProficiencyType.NONE, this::makeDirty);
|
||||
mName = new ChangeTrackedLiveData<>("New Skill", this::makeDirty);
|
||||
mSkill = new ChangeTrackedLiveData<>(makeSkill(), this::makeDirty);
|
||||
}
|
||||
|
||||
public void copyFromSkill(@NonNull Skill skill) {
|
||||
mAbilityScore.resetValue(skill.abilityScore);
|
||||
mAdvantageType.resetValue(skill.advantageType);
|
||||
mProficiencyType.resetValue(skill.proficiencyType);
|
||||
mName.resetValue(skill.name);
|
||||
makeClean();
|
||||
}
|
||||
|
||||
public LiveData<Skill> getSkill() {
|
||||
return mSkill;
|
||||
}
|
||||
|
||||
public LiveData<AbilityScore> getAbilityScore() {
|
||||
return mAbilityScore;
|
||||
}
|
||||
|
||||
public void setAbilityScore(AbilityScore value) {
|
||||
mAbilityScore.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<AdvantageType> getAdvantage() {
|
||||
return mAdvantageType;
|
||||
}
|
||||
|
||||
public void setAdvantage(AdvantageType value) {
|
||||
mAdvantageType.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<ProficiencyType> getProficiency() {
|
||||
return mProficiencyType;
|
||||
}
|
||||
|
||||
public void setProficiency(ProficiencyType value) {
|
||||
mProficiencyType.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String value) {
|
||||
mName.setValue(value);
|
||||
mSkill.setValue(makeSkill());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Skill makeSkill() {
|
||||
return new Skill(mName.getValue(), mAbilityScore.getValue(), mAdvantageType.getValue(), mProficiencyType.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
/**
|
||||
* A fragment representing a list of Items.
|
||||
*/
|
||||
public class EditSkillsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private void navigateToEditSkill(@NonNull Skill skill) {
|
||||
NavDirections action = EditSkillsFragmentDirections.actionEditSkillsFragmentToEditSkillFragment(skill.name, skill.abilityScore, skill.proficiencyType, skill.advantageType);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_skills_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddSkillButton(mHolder.addSkill);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> {
|
||||
EditSkillsRecyclerViewAdapter adapter = new EditSkillsRecyclerViewAdapter(mViewModel.getSkillsArray(), skill -> {
|
||||
if (skill != null) {
|
||||
navigateToEditSkill(skill);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditSkill with a null skill");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeSkill(position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddSkillButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Skill newSkill = mViewModel.addNewSkill();
|
||||
navigateToEditSkill(newSkill);
|
||||
});
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addSkill;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
this.list = root.findViewById(R.id.list);
|
||||
this.addSkill = root.findViewById(R.id.add_skill);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditSkillsListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Skill;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link RecyclerView.Adapter} that can display a {@link Skill}.
|
||||
*/
|
||||
public class EditSkillsRecyclerViewAdapter extends RecyclerView.Adapter<EditSkillsRecyclerViewAdapter.ViewHolder> {
|
||||
private final List<Skill> mValues;
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
public EditSkillsRecyclerViewAdapter(List<Skill> items, ItemCallback onClick) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(FragmentEditSkillsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mContentView.setText(mValues.get(position).name);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Skill skill);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Skill mItem;
|
||||
|
||||
public ViewHolder(@NonNull FragmentEditSkillsListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.ui.components.Stepper;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditSpeedFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_speed, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mHolder.baseSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setWalkSpeed(newValue));
|
||||
mHolder.baseSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getWalkSpeed().observe(getViewLifecycleOwner(), value -> mHolder.baseSpeed.setValue(value));
|
||||
|
||||
mHolder.burrowSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setBurrowSpeed(newValue));
|
||||
mHolder.burrowSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getBurrowSpeed().observe(getViewLifecycleOwner(), value -> mHolder.burrowSpeed.setValue(value));
|
||||
|
||||
mHolder.climbSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setClimbSpeed(newValue));
|
||||
mHolder.climbSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getClimbSpeed().observe(getViewLifecycleOwner(), value -> mHolder.climbSpeed.setValue(value));
|
||||
|
||||
mHolder.flySpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setFlySpeed(newValue));
|
||||
mHolder.flySpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getFlySpeed().observe(getViewLifecycleOwner(), value -> mHolder.flySpeed.setValue(value));
|
||||
|
||||
mHolder.swimSpeed.setOnValueChangeListener((newValue, oldValue) -> mViewModel.setSwimSpeed(newValue));
|
||||
mHolder.swimSpeed.setOnFormatValueCallback(value -> String.format(getString(R.string.format_distance_in_feet), value));
|
||||
mViewModel.getSwimSpeed().observe(getViewLifecycleOwner(), value -> mHolder.swimSpeed.setValue(value));
|
||||
|
||||
mViewModel.getCanHover().observe(getViewLifecycleOwner(), value -> mHolder.canHover.setChecked(value));
|
||||
mHolder.canHover.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setCanHover(isChecked));
|
||||
|
||||
mViewModel.getHasCustomSpeed().observe(getViewLifecycleOwner(), value -> mHolder.hasCustomSpeed.setChecked(value));
|
||||
mHolder.hasCustomSpeed.setOnCheckedChangeListener((buttonView, isChecked) -> mViewModel.setHasCustomSpeed(isChecked));
|
||||
|
||||
//mViewModel.getCustomSpeed().observe(getViewLifecycleOwner(), value -> mHolder.customSpeed.setText(value));
|
||||
mHolder.customSpeed.setText(mViewModel.getCustomSpeed().getValue());
|
||||
mHolder.customSpeed.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setCustomSpeed(s.toString())));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
final Stepper baseSpeed;
|
||||
final Stepper burrowSpeed;
|
||||
final Stepper climbSpeed;
|
||||
final Stepper flySpeed;
|
||||
final Stepper swimSpeed;
|
||||
final SwitchCompat canHover;
|
||||
final SwitchCompat hasCustomSpeed;
|
||||
final EditText customSpeed;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
baseSpeed = root.findViewById(R.id.baseSpeed);
|
||||
burrowSpeed = root.findViewById(R.id.burrowSpeed);
|
||||
climbSpeed = root.findViewById(R.id.climbSpeed);
|
||||
flySpeed = root.findViewById(R.id.flySpeed);
|
||||
swimSpeed = root.findViewById(R.id.swimSpeed);
|
||||
canHover = root.findViewById(R.id.canHover);
|
||||
hasCustomSpeed = root.findViewById(R.id.hasCustomSpeed);
|
||||
customSpeed = root.findViewById(R.id.customSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditStringFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditStringViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private String mOldValue;
|
||||
private StringType mStringType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditStringViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditStringFragmentArgs args = EditStringFragmentArgs.fromBundle(getArguments());
|
||||
mOldValue = args.getValue();
|
||||
mViewModel.setValue(mOldValue);
|
||||
mStringType = args.getStringType();
|
||||
} else {
|
||||
Logger.logWTF("EditStringFragment needs arguments");
|
||||
mOldValue = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_string, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForStringType(mStringType));
|
||||
|
||||
mHolder.description.setText(mViewModel.getValueAsString());
|
||||
mHolder.description.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setValue(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceString(mStringType, mOldValue, mViewModel.getValueAsString());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForStringType(@NonNull StringType type) {
|
||||
switch (type) {
|
||||
case CONDITION_IMMUNITY:
|
||||
return getString(R.string.title_editConditionImmunity);
|
||||
case DAMAGE_IMMUNITY:
|
||||
return getString(R.string.title_editDamageImmunity);
|
||||
case DAMAGE_RESISTANCE:
|
||||
return getString(R.string.title_editDamageResistance);
|
||||
case DAMAGE_VULNERABILITY:
|
||||
return getString(R.string.title_editDamageVulnerability);
|
||||
case SENSE:
|
||||
return getString(R.string.title_editSense);
|
||||
default:
|
||||
return getString(R.string.title_editString);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.description.requestFocus();
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText description;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
description = root.findViewById(R.id.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditStringViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mValue;
|
||||
|
||||
public EditStringViewModel() {
|
||||
super();
|
||||
mValue = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
}
|
||||
|
||||
public LiveData<String> getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
mValue.setValue(value);
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
return mValue.getValue();
|
||||
}
|
||||
|
||||
public void resetValue(String value) {
|
||||
makeClean();
|
||||
mValue.resetValue(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.StringType;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditStringsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private StringType mStringType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
EditStringsFragmentArgs args = EditStringsFragmentArgs.fromBundle(arguments);
|
||||
mStringType = args.getStringType();
|
||||
} else {
|
||||
Logger.logWTF("EditStringsFragment needs arguments");
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_strings_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForStringType(mStringType));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddButton(mHolder.addItem);
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForStringType(StringType type) {
|
||||
switch (type) {
|
||||
case CONDITION_IMMUNITY:
|
||||
return getString(R.string.title_editConditionImmunities);
|
||||
case DAMAGE_IMMUNITY:
|
||||
return getString(R.string.title_editDamageImmunities);
|
||||
case DAMAGE_RESISTANCE:
|
||||
return getString(R.string.title_editDamageResistances);
|
||||
case DAMAGE_VULNERABILITY:
|
||||
return getString(R.string.title_editDamageVulnerabilities);
|
||||
case SENSE:
|
||||
return getString(R.string.title_editSenses);
|
||||
default:
|
||||
return getString(R.string.title_editStrings);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<String>> stringsData = mViewModel.getStrings(mStringType);
|
||||
if (stringsData != null) {
|
||||
stringsData.observe(getViewLifecycleOwner(), strings -> {
|
||||
EditStringsRecyclerViewAdapter adapter = new EditStringsRecyclerViewAdapter(strings, value -> {
|
||||
if (value != null) {
|
||||
navigateToEditString(value);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditStringFragment with a null trait");
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
});
|
||||
}
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeString(mStringType, position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
String newValue = mViewModel.addNewString(mStringType);
|
||||
if (newValue != null) {
|
||||
navigateToEditString(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditString(String value) {
|
||||
NavDirections action = EditStringsFragmentDirections.actionEditStringsFragmentToEditStringFragment(mStringType, value);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addItem;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
addItem = root.findViewById(R.id.add_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditStringsListItemBinding;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditStringsRecyclerViewAdapter extends RecyclerView.Adapter<EditStringsRecyclerViewAdapter.ViewHolder> {
|
||||
private final List<String> mValues;
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
public EditStringsRecyclerViewAdapter(List<String> items, ItemCallback onClick) {
|
||||
mValues = items;
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(FragmentEditStringsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = mValues.get(position);
|
||||
holder.mContentView.setText(mValues.get(position));
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(String value);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public String mItem;
|
||||
|
||||
public ViewHolder(@NonNull FragmentEditStringsListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
import com.majinnaibu.monstercards.utils.TextChangedListener;
|
||||
|
||||
public class EditTraitFragment extends MCFragment {
|
||||
private EditMonsterViewModel mEditMonsterViewModel;
|
||||
private EditTraitViewModel mViewModel;
|
||||
private EditTraitFragment.ViewHolder mHolder;
|
||||
private Trait mOldValue;
|
||||
private TraitType mTraitType;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
mViewModel = new ViewModelProvider(this).get(EditTraitViewModel.class);
|
||||
if (getArguments() != null) {
|
||||
EditTraitFragmentArgs args = EditTraitFragmentArgs.fromBundle(getArguments());
|
||||
mOldValue = new Trait(args.getName(), args.getDescription());
|
||||
mViewModel.copyFromTrait(mOldValue);
|
||||
mTraitType = args.getTraitType();
|
||||
} else {
|
||||
Logger.logWTF("EditTraitFragment needs arguments");
|
||||
mOldValue = null;
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mEditMonsterViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_trait, container, false);
|
||||
mHolder = new EditTraitFragment.ViewHolder(root);
|
||||
setTitle(getTitleForTraitType(mTraitType));
|
||||
|
||||
mHolder.name.setText(mViewModel.getNameAsString());
|
||||
mHolder.name.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setName(s.toString())));
|
||||
|
||||
mHolder.description.setText(mViewModel.getDescriptionAsString());
|
||||
mHolder.description.addTextChangedListener(new TextChangedListener((TextChangedListener.OnTextChangedCallback) (s, start, before, count) -> mViewModel.setDescription(s.toString())));
|
||||
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (mViewModel.hasChanges()) {
|
||||
mEditMonsterViewModel.replaceTrait(mTraitType, mOldValue, mViewModel.getAbilityValue());
|
||||
}
|
||||
Navigation.findNavController(requireView()).navigateUp();
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHolder.name.requestFocus();
|
||||
}
|
||||
|
||||
private String getTitleForTraitType(TraitType type) {
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
return getString(R.string.title_editAbility);
|
||||
case ACTION:
|
||||
return getString(R.string.title_editAction);
|
||||
case LAIR_ACTION:
|
||||
return getString(R.string.title_editLairAction);
|
||||
case LEGENDARY_ACTION:
|
||||
return getString(R.string.title_editLegendaryAction);
|
||||
case REACTIONS:
|
||||
return getString(R.string.title_editReaction);
|
||||
case REGIONAL_ACTION:
|
||||
return getString(R.string.title_editRegionalAction);
|
||||
default:
|
||||
return getString(R.string.title_editTrait);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
EditText description;
|
||||
EditText name;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
description = root.findViewById(R.id.description);
|
||||
name = root.findViewById(R.id.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.ChangeTrackedViewModel;
|
||||
import com.majinnaibu.monstercards.utils.ChangeTrackedLiveData;
|
||||
|
||||
public class EditTraitViewModel extends ChangeTrackedViewModel {
|
||||
private final ChangeTrackedLiveData<String> mName;
|
||||
private final ChangeTrackedLiveData<String> mDescription;
|
||||
private final MutableLiveData<Trait> mAbility;
|
||||
|
||||
public EditTraitViewModel() {
|
||||
super();
|
||||
mName = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
mDescription = new ChangeTrackedLiveData<>("", this::makeDirty);
|
||||
mAbility = new MutableLiveData<>(makeAbility());
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName.setValue(name);
|
||||
mAbility.setValue(makeAbility());
|
||||
}
|
||||
|
||||
public String getNameAsString() {
|
||||
return mName.getValue();
|
||||
}
|
||||
|
||||
public LiveData<String> getDescription() {
|
||||
return mDescription;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
mDescription.setValue(description);
|
||||
mAbility.setValue(makeAbility());
|
||||
}
|
||||
|
||||
public String getDescriptionAsString() {
|
||||
return mDescription.getValue();
|
||||
}
|
||||
|
||||
public Trait getAbilityValue() {
|
||||
return mAbility.getValue();
|
||||
}
|
||||
|
||||
public void copyFromTrait(@NonNull Trait trait) {
|
||||
makeClean();
|
||||
mName.resetValue(trait.name);
|
||||
mDescription.resetValue(trait.description);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Trait makeAbility() {
|
||||
return new Trait(mName.getValue(), mDescription.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavBackStackEntry;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.enums.TraitType;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EditTraitsFragment extends MCFragment {
|
||||
private EditMonsterViewModel mViewModel;
|
||||
private ViewHolder mHolder;
|
||||
private TraitType mTraitType;
|
||||
private EditTraitsRecyclerViewAdapter mAdapter;
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
if (getArguments() != null) {
|
||||
EditTraitsFragmentArgs args = EditTraitsFragmentArgs.fromBundle(getArguments());
|
||||
mTraitType = args.getTraitType();
|
||||
} else {
|
||||
Logger.logWTF("EditTraitFragment needs arguments");
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
NavController navController = Navigation.findNavController(requireActivity(), R.id.nav_host_fragment);
|
||||
NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.edit_monster_navigation);
|
||||
mViewModel = new ViewModelProvider(backStackEntry).get(EditMonsterViewModel.class);
|
||||
View root = inflater.inflate(R.layout.fragment_edit_traits_list, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
setTitle(getTitleForTraitType(mTraitType));
|
||||
setupRecyclerView(mHolder.list);
|
||||
setupAddButton(mHolder.addTrait);
|
||||
return root;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getTitleForTraitType(TraitType type) {
|
||||
switch (type) {
|
||||
case ABILITY:
|
||||
return getString(R.string.title_editAbilities);
|
||||
case ACTION:
|
||||
return getString(R.string.title_editActions);
|
||||
case LAIR_ACTION:
|
||||
return getString(R.string.title_editLairActions);
|
||||
case LEGENDARY_ACTION:
|
||||
return getString(R.string.title_editLegendaryActions);
|
||||
case REACTIONS:
|
||||
return getString(R.string.title_editReactions);
|
||||
case REGIONAL_ACTION:
|
||||
return getString(R.string.title_editRegionalActions);
|
||||
default:
|
||||
return getString(R.string.title_editTraits);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
LiveData<List<Trait>> traitData = mViewModel.getTraits(mTraitType);
|
||||
mAdapter = new EditTraitsRecyclerViewAdapter(trait -> {
|
||||
if (trait != null) {
|
||||
navigateToEditTrait(trait);
|
||||
} else {
|
||||
Logger.logError("Can't navigate to EditTraitFragment with a null trait");
|
||||
}
|
||||
});
|
||||
if (traitData != null) {
|
||||
traitData.observe(getViewLifecycleOwner(), traits -> mAdapter.submitList(traits));
|
||||
}
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(context, (position, direction) -> mViewModel.removeTrait(mTraitType, position), (from, to) -> mViewModel.moveTrait(mTraitType, from, to)));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Trait newTrait = mViewModel.addNewTrait(mTraitType);
|
||||
if (newTrait != null) {
|
||||
navigateToEditTrait(newTrait);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToEditTrait(@NonNull Trait trait) {
|
||||
NavDirections action = EditTraitsFragmentDirections.actionEditTraitListFragmentToEditTraitFragment(trait.description, trait.name, mTraitType);
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
RecyclerView list;
|
||||
FloatingActionButton addTrait;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
list = root.findViewById(R.id.list);
|
||||
addTrait = root.findViewById(R.id.add_trait);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.majinnaibu.monstercards.ui.editmonster;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.databinding.FragmentEditTraitsListItemBinding;
|
||||
import com.majinnaibu.monstercards.models.Trait;
|
||||
|
||||
public class EditTraitsRecyclerViewAdapter extends ListAdapter<Trait, EditTraitsRecyclerViewAdapter.ViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Trait> DIFF_CALLBACK = new DiffUtil.ItemCallback<Trait>() {
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Trait oldItem, @NonNull Trait newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Trait oldItem, @NonNull Trait newItem) {
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
};
|
||||
private final ItemCallback mOnClick;
|
||||
|
||||
protected EditTraitsRecyclerViewAdapter(ItemCallback onClick) {
|
||||
super(DIFF_CALLBACK);
|
||||
mOnClick = onClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new ViewHolder(FragmentEditTraitsListItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
holder.mItem = getItem(position);
|
||||
holder.mContentView.setText(holder.mItem.name);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(holder.mItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Trait trait);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public final TextView mContentView;
|
||||
public Trait mItem;
|
||||
|
||||
public ViewHolder(@NonNull FragmentEditTraitsListItemBinding binding) {
|
||||
super(binding.getRoot());
|
||||
mContentView = binding.content;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " '" + mContentView.getText() + "'";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.majinnaibu.monstercards.ui.library;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.ui.shared.SwipeToDeleteCallback;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class LibraryFragment extends MCFragment {
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_library, container, false);
|
||||
|
||||
FloatingActionButton fab = root.findViewById(R.id.fab);
|
||||
assert fab != null;
|
||||
setupAddMonsterButton(fab);
|
||||
|
||||
final RecyclerView recyclerView = root.findViewById(R.id.monster_list);
|
||||
assert recyclerView != null;
|
||||
setupRecyclerView(recyclerView);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
Context context = requireContext();
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
|
||||
LibraryRecyclerViewAdapter adapter = new LibraryRecyclerViewAdapter(
|
||||
context,
|
||||
repository.getMonsters(),
|
||||
(monster) -> navigateToMonsterDetail(monster.id),
|
||||
(monster) -> repository
|
||||
.deleteMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
}
|
||||
}));
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(context, layoutManager.getOrientation());
|
||||
recyclerView.addItemDecoration(dividerItemDecoration);
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new SwipeToDeleteCallback(requireContext(), (position, direction) -> adapter.deleteItem(position), null));
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
}
|
||||
|
||||
private void setupAddMonsterButton(@NonNull FloatingActionButton fab) {
|
||||
fab.setOnClickListener(view -> {
|
||||
Monster monster = new Monster();
|
||||
monster.name = getString(R.string.default_monster_name);
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
repository.addMonster(monster)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Snackbar.make(
|
||||
view,
|
||||
getString(R.string.snackbar_monster_created, monster.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", (_view) -> navigateToMonsterDetail(monster.id))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
View view = getView();
|
||||
assert view != null;
|
||||
Snackbar.make(view, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", null).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected void navigateToMonsterDetail(@NonNull UUID monsterId) {
|
||||
NavDirections action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.majinnaibu.monstercards.ui.library;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class LibraryRecyclerViewAdapter extends RecyclerView.Adapter<LibraryRecyclerViewAdapter.ViewHolder> {
|
||||
private final Context mContext;
|
||||
private final ItemCallback mOnDelete;
|
||||
private final ItemCallback mOnClick;
|
||||
private final Flowable<List<Monster>> mItemsObservable;
|
||||
private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(@NonNull View view) {
|
||||
Monster monster = (Monster) view.getTag();
|
||||
if (mOnClick != null) {
|
||||
mOnClick.onItemCallback(monster);
|
||||
}
|
||||
}
|
||||
};
|
||||
private List<Monster> mValues;
|
||||
private Disposable mDisposable;
|
||||
|
||||
public LibraryRecyclerViewAdapter(Context context,
|
||||
Flowable<List<Monster>> itemsObservable,
|
||||
ItemCallback onClick,
|
||||
ItemCallback onDelete) {
|
||||
mItemsObservable = itemsObservable;
|
||||
mValues = new ArrayList<>();
|
||||
mContext = context;
|
||||
mOnDelete = onDelete;
|
||||
mOnClick = onClick;
|
||||
mDisposable = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.monster_list_content, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final @NonNull ViewHolder holder, int position) {
|
||||
Monster monster = mValues.get(position);
|
||||
holder.mContentView.setText(monster.name);
|
||||
holder.itemView.setTag(monster);
|
||||
holder.itemView.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
// TODO: consider moving this subscription out of the adapter and make the subscriber call setItems on the adapter
|
||||
mDisposable = mItemsObservable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(monsters -> {
|
||||
mValues = monsters;
|
||||
notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
|
||||
super.onDetachedFromRecyclerView(recyclerView);
|
||||
mDisposable.dispose();
|
||||
}
|
||||
|
||||
public void deleteItem(int position) {
|
||||
if (mOnDelete != null) {
|
||||
Monster monster = mValues.get(position);
|
||||
mOnDelete.onItemCallback(monster);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItemCallback(Monster monster);
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView mContentView;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
mContentView = view.findViewById(R.id.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
|
||||
|
||||
public class MonsterDetailFragment extends MCFragment {
|
||||
private ViewHolder mHolder;
|
||||
|
||||
private MonsterDetailViewModel mViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
MonsterRepository repository = getMonsterRepository();
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
UUID monsterId = UUID.fromString(MonsterDetailFragmentArgs.fromBundle(arguments).getMonsterId());
|
||||
setHasOptionsMenu(true);
|
||||
mViewModel = new ViewModelProvider(this).get(MonsterDetailViewModel.class);
|
||||
repository.getMonster(monsterId).toObservable()
|
||||
.firstOrError()
|
||||
.subscribe(new DisposableSingleObserver<Monster>() {
|
||||
@Override
|
||||
public void onSuccess(@io.reactivex.rxjava3.annotations.NonNull Monster monster) {
|
||||
mViewModel.setMonster(monster);
|
||||
dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError(e);
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
View root = inflater.inflate(R.layout.fragment_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getName().observe(getViewLifecycleOwner(), name -> {
|
||||
mHolder.name.setText(name);
|
||||
setTitle(getString(R.string.title_monsterDetails_fmt, name));
|
||||
});
|
||||
mViewModel.getMeta().observe(getViewLifecycleOwner(), mHolder.meta::setText);
|
||||
mViewModel.getArmorClass().observe(getViewLifecycleOwner(), armorText -> setupLabeledTextView(mHolder.armorClass, armorText, R.string.label_armor_class));
|
||||
mViewModel.getHitPoints().observe(getViewLifecycleOwner(), hitPoints -> setupLabeledTextView(mHolder.hitPoints, hitPoints, R.string.label_hit_points));
|
||||
mViewModel.getSpeed().observe(getViewLifecycleOwner(), speed -> setupLabeledTextView(mHolder.speed, speed, R.string.label_speed));
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), mHolder.strength::setText);
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), mHolder.dexterity::setText);
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), mHolder.constitution::setText);
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), mHolder.intelligence::setText);
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), mHolder.wisdom::setText);
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), mHolder.charisma::setText);
|
||||
mViewModel.getSavingThrows().observe(getViewLifecycleOwner(), savingThrows -> setupOptionalTextView(mHolder.savingThrows, savingThrows, R.string.label_saving_throws));
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> setupOptionalTextView(mHolder.skills, skills, R.string.label_skills));
|
||||
mViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageVulnerabilities, damageTypes, R.string.label_damage_vulnerabilities));
|
||||
mViewModel.getDamageResistances().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageResistances, damageTypes, R.string.label_damage_resistances));
|
||||
mViewModel.getDamageImmunities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageImmunities, damageTypes, R.string.label_damage_immunities));
|
||||
mViewModel.getConditionImmunities().observe(getViewLifecycleOwner(), conditionImmunities -> setupOptionalTextView(mHolder.conditionImmunities, conditionImmunities, R.string.label_condition_immunities));
|
||||
mViewModel.getSenses().observe(getViewLifecycleOwner(), senses -> setupOptionalTextView(mHolder.senses, senses, R.string.label_senses));
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> setupOptionalTextView(mHolder.languages, languages, R.string.label_languages));
|
||||
mViewModel.getChallenge().observe(getViewLifecycleOwner(), challengeRating -> setupLabeledTextView(mHolder.challenge, challengeRating, R.string.label_challenge_rating));
|
||||
mViewModel.getAbilities().observe(getViewLifecycleOwner(), abilities -> setupTraitList(mHolder.abilities, abilities));
|
||||
mViewModel.getActions().observe(getViewLifecycleOwner(), actions -> setupTraitList(mHolder.actions, actions, mHolder.actions_label, mHolder.actions_divider));
|
||||
mViewModel.getReactions().observe(getViewLifecycleOwner(), reactions -> setupTraitList(mHolder.reactions, reactions, mHolder.reactions_label, mHolder.reactions_divider));
|
||||
mViewModel.getRegionalEffects().observe(getViewLifecycleOwner(), regionalEffects -> setupTraitList(mHolder.regionalEffects, regionalEffects, mHolder.regionalEffects_label, mHolder.regionalEffects_divider));
|
||||
mViewModel.getLairActions().observe(getViewLifecycleOwner(), lairActions -> setupTraitList(mHolder.lairActions, lairActions, mHolder.lairActions_label, mHolder.lairActions_divider));
|
||||
mViewModel.getLegendaryActions().observe(getViewLifecycleOwner(), legendaryActions -> setupTraitList(mHolder.legendaryActions, legendaryActions, mHolder.legendaryActions_label, mHolder.legendaryActions_divider));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupLabeledTextView(@NonNull TextView view, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
String fullText = String.format("<b>%s</b> %s", title, text);
|
||||
view.setText(Html.fromHtml(fullText));
|
||||
}
|
||||
|
||||
private void setupOptionalTextView(TextView root, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
if (StringHelper.isNullOrEmpty(text)) {
|
||||
root.setVisibility(View.GONE);
|
||||
} else {
|
||||
root.setVisibility(View.VISIBLE);
|
||||
}
|
||||
Spanned formatted;
|
||||
if (StringHelper.isNullOrEmpty(title)) {
|
||||
formatted = Html.fromHtml(text);
|
||||
} else {
|
||||
formatted = Html.fromHtml(String.format("<b>%s</b> %s", title, text));
|
||||
}
|
||||
root.setText(formatted);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits) {
|
||||
setupTraitList(root, traits, null, null);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits, View label, View divider) {
|
||||
int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE;
|
||||
Context context = getContext();
|
||||
DisplayMetrics displayMetrics = null;
|
||||
if (context != null) {
|
||||
Resources resources = context.getResources();
|
||||
if (resources != null) {
|
||||
displayMetrics = resources.getDisplayMetrics();
|
||||
}
|
||||
}
|
||||
root.removeAllViews();
|
||||
for (String action : traits) {
|
||||
TextView tvAction = new TextView(getContext());
|
||||
// TODO: Handle multiline block quotes specially so they stay multiline.
|
||||
// TODO: Replace QuoteSpans in the result of fromHtml with something like this https://stackoverflow.com/questions/7717567/how-to-style-blockquotes-in-android-textviews to make them indent as expected
|
||||
tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
|
||||
tvAction.setLayoutParams(layoutParams);
|
||||
root.addView(tvAction);
|
||||
}
|
||||
root.setVisibility(visibility);
|
||||
if (label != null) {
|
||||
label.setVisibility(visibility);
|
||||
}
|
||||
if (divider != null) {
|
||||
divider.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.monster_detail_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_action_edit_monster) {
|
||||
UUID monsterId = mViewModel.getId().getValue();
|
||||
if (monsterId != null) {
|
||||
NavDirections action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
|
||||
Navigation.findNavController(requireView()).navigate(action);
|
||||
} else {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final TextView name;
|
||||
final TextView meta;
|
||||
final TextView armorClass;
|
||||
final TextView hitPoints;
|
||||
final TextView speed;
|
||||
final TextView strength;
|
||||
final TextView dexterity;
|
||||
final TextView constitution;
|
||||
final TextView intelligence;
|
||||
final TextView wisdom;
|
||||
final TextView charisma;
|
||||
final TextView savingThrows;
|
||||
final TextView skills;
|
||||
final TextView damageVulnerabilities;
|
||||
final TextView damageResistances;
|
||||
final TextView damageImmunities;
|
||||
final TextView conditionImmunities;
|
||||
final TextView senses;
|
||||
final TextView languages;
|
||||
final TextView challenge;
|
||||
final LinearLayout abilities;
|
||||
final LinearLayout actions;
|
||||
final TextView actions_label;
|
||||
final ImageView actions_divider;
|
||||
final LinearLayout reactions;
|
||||
final TextView reactions_label;
|
||||
final ImageView reactions_divider;
|
||||
final LinearLayout legendaryActions;
|
||||
final TextView legendaryActions_label;
|
||||
final ImageView legendaryActions_divider;
|
||||
final LinearLayout lairActions;
|
||||
final TextView lairActions_label;
|
||||
final ImageView lairActions_divider;
|
||||
final LinearLayout regionalEffects;
|
||||
final TextView regionalEffects_label;
|
||||
final ImageView regionalEffects_divider;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
name = root.findViewById(R.id.name);
|
||||
meta = root.findViewById(R.id.meta);
|
||||
armorClass = root.findViewById(R.id.armorClass);
|
||||
hitPoints = root.findViewById(R.id.hitPoints);
|
||||
speed = root.findViewById(R.id.speed);
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challenge = root.findViewById(R.id.challenge);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
actions_divider = root.findViewById(R.id.actions_divider);
|
||||
actions_label = root.findViewById(R.id.actions_label);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
reactions_divider = root.findViewById(R.id.reactions_divider);
|
||||
reactions_label = root.findViewById(R.id.reactions_label);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
legendaryActions_divider = root.findViewById(R.id.legendaryActions_divider);
|
||||
legendaryActions_label = root.findViewById(R.id.legendaryActions_label);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
lairActions_divider = root.findViewById(R.id.lairActions_divider);
|
||||
lairActions_label = root.findViewById(R.id.lairActions_label);
|
||||
regionalEffects = root.findViewById(R.id.regionalEffects);
|
||||
regionalEffects_divider = root.findViewById(R.id.regionalEffects_divider);
|
||||
regionalEffects_label = root.findViewById(R.id.regionalEffects_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MonsterDetailViewModel extends ViewModel {
|
||||
|
||||
private final MutableLiveData<List<String>> mAbilities;
|
||||
private final MutableLiveData<List<String>> mActions;
|
||||
private final MutableLiveData<String> mArmorClass;
|
||||
private final MutableLiveData<String> mChallenge;
|
||||
private final MutableLiveData<String> mCharisma;
|
||||
private final MutableLiveData<String> mConditionImmunities;
|
||||
private final MutableLiveData<String> mConstitution;
|
||||
private final MutableLiveData<String> mDamageResistances;
|
||||
private final MutableLiveData<String> mDamageImmunities;
|
||||
private final MutableLiveData<String> mDamageVulnerabilities;
|
||||
private final MutableLiveData<String> mDexterity;
|
||||
private final MutableLiveData<String> mHitPoints;
|
||||
private final MutableLiveData<String> mIntelligence;
|
||||
private final MutableLiveData<List<String>> mLairActions;
|
||||
private final MutableLiveData<String> mLanguages;
|
||||
private final MutableLiveData<List<String>> mLegendaryActions;
|
||||
private final MutableLiveData<String> mMeta;
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<List<String>> mReactions;
|
||||
private final MutableLiveData<List<String>> mRegionalEffects;
|
||||
private final MutableLiveData<String> mSavingThrows;
|
||||
private final MutableLiveData<String> mSenses;
|
||||
private final MutableLiveData<String> mSkills;
|
||||
private final MutableLiveData<String> mSpeed;
|
||||
private final MutableLiveData<String> mStrength;
|
||||
private final MutableLiveData<String> mWisdom;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
private Monster mMonster;
|
||||
|
||||
public MonsterDetailViewModel() {
|
||||
mMonster = null;
|
||||
mAbilities = new MutableLiveData<>(new ArrayList<>());
|
||||
mActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mArmorClass = new MutableLiveData<>("");
|
||||
mChallenge = new MutableLiveData<>("");
|
||||
mCharisma = new MutableLiveData<>("");
|
||||
mConditionImmunities = new MutableLiveData<>("");
|
||||
mConstitution = new MutableLiveData<>("");
|
||||
mDamageImmunities = new MutableLiveData<>("");
|
||||
mDamageResistances = new MutableLiveData<>("");
|
||||
mDamageVulnerabilities = new MutableLiveData<>("");
|
||||
mDexterity = new MutableLiveData<>("");
|
||||
mHitPoints = new MutableLiveData<>("");
|
||||
mIntelligence = new MutableLiveData<>("");
|
||||
mLairActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mLanguages = new MutableLiveData<>("");
|
||||
mLegendaryActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mMeta = new MutableLiveData<>("");
|
||||
mName = new MutableLiveData<>("");
|
||||
mReactions = new MutableLiveData<>(new ArrayList<>());
|
||||
mRegionalEffects = new MutableLiveData<>(new ArrayList<>());
|
||||
mSavingThrows = new MutableLiveData<>("");
|
||||
mSenses = new MutableLiveData<>("");
|
||||
mSkills = new MutableLiveData<>("");
|
||||
mSpeed = new MutableLiveData<>("");
|
||||
mStrength = new MutableLiveData<>("");
|
||||
mWisdom = new MutableLiveData<>("");
|
||||
mMonsterId = new MutableLiveData<>(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getAbilities() {
|
||||
return mAbilities;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getActions() {
|
||||
return mActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getReactions() {
|
||||
return mReactions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLegendaryActions() {
|
||||
return mLegendaryActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLairActions() {
|
||||
return mLairActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getRegionalEffects() {
|
||||
return mRegionalEffects;
|
||||
}
|
||||
|
||||
public LiveData<String> getArmorClass() {
|
||||
return mArmorClass;
|
||||
}
|
||||
|
||||
public LiveData<String> getChallenge() {
|
||||
return mChallenge;
|
||||
}
|
||||
|
||||
public LiveData<String> getCharisma() {
|
||||
return mCharisma;
|
||||
}
|
||||
|
||||
public LiveData<String> getConditionImmunities() {
|
||||
return mConditionImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getConstitution() {
|
||||
return mConstitution;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageResistances() {
|
||||
return mDamageResistances;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageImmunities() {
|
||||
return mDamageImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageVulnerabilities() {
|
||||
return mDamageVulnerabilities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDexterity() {
|
||||
return mDexterity;
|
||||
}
|
||||
|
||||
public LiveData<String> getHitPoints() {
|
||||
return mHitPoints;
|
||||
}
|
||||
|
||||
public LiveData<String> getIntelligence() {
|
||||
return mIntelligence;
|
||||
}
|
||||
|
||||
public LiveData<String> getLanguages() {
|
||||
return mLanguages;
|
||||
}
|
||||
|
||||
public LiveData<String> getMeta() {
|
||||
return mMeta;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public LiveData<String> getSavingThrows() {
|
||||
return mSavingThrows;
|
||||
}
|
||||
|
||||
public LiveData<String> getSenses() {
|
||||
return mSenses;
|
||||
}
|
||||
|
||||
public LiveData<String> getSkills() {
|
||||
return mSkills;
|
||||
}
|
||||
|
||||
public LiveData<String> getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
public LiveData<String> getStrength() {
|
||||
return mStrength;
|
||||
}
|
||||
|
||||
public LiveData<String> getWisdom() {
|
||||
return mWisdom;
|
||||
}
|
||||
|
||||
public LiveData<UUID> getId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
|
||||
public void setMonster(@NonNull Monster monster) {
|
||||
mMonster = monster;
|
||||
mAbilities.setValue(mMonster.getAbilityDescriptions());
|
||||
mActions.setValue(mMonster.getActionDescriptions());
|
||||
mArmorClass.setValue(mMonster.getArmorClass());
|
||||
mChallenge.setValue(mMonster.getChallengeRatingDescription());
|
||||
mCharisma.setValue(monster.getCharismaDescription());
|
||||
mConditionImmunities.setValue(mMonster.getConditionImmunitiesDescription());
|
||||
mConstitution.setValue(monster.getConstitutionDescription());
|
||||
mDamageImmunities.setValue(mMonster.getDamageImmunitiesDescription());
|
||||
mDamageResistances.setValue(mMonster.getDamageResistancesDescription());
|
||||
mDamageVulnerabilities.setValue(mMonster.getDamageVulnerabilitiesDescription());
|
||||
mDexterity.setValue(monster.getDexterityDescription());
|
||||
mHitPoints.setValue(mMonster.getHitPoints());
|
||||
mIntelligence.setValue(monster.getIntelligenceDescription());
|
||||
mLairActions.setValue(mMonster.getLairActionDescriptions());
|
||||
mLanguages.setValue(mMonster.getLanguagesDescription());
|
||||
mLegendaryActions.setValue(mMonster.getLegendaryActionDescriptions());
|
||||
mMeta.setValue(mMonster.getMeta());
|
||||
mMonsterId.setValue(mMonster.id);
|
||||
mName.setValue(mMonster.name);
|
||||
mReactions.setValue(monster.getReactionDescriptions());
|
||||
mRegionalEffects.setValue(monster.getRegionalActionDescriptions());
|
||||
mSavingThrows.setValue(monster.getSavingThrowsDescription());
|
||||
mSenses.setValue(monster.getSensesDescription());
|
||||
mSkills.setValue(monster.getSkillsDescription());
|
||||
mSpeed.setValue(mMonster.getSpeedText());
|
||||
mStrength.setValue(monster.getStrengthDescription());
|
||||
mWisdom.setValue(monster.getWisdomDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavDirections;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.helpers.CommonMarkHelper;
|
||||
import com.majinnaibu.monstercards.helpers.MonsterImportHelper;
|
||||
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.ui.library.LibraryFragmentDirections;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.observers.DisposableCompletableObserver;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class MonsterImportFragment extends MCFragment {
|
||||
private ViewHolder mHolder;
|
||||
private MonsterImportViewModel mViewModel;
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
Logger.logDebug("MonsterCards: loading monster for import");
|
||||
Bundle arguments = getArguments();
|
||||
assert arguments != null;
|
||||
String json = MonsterImportFragmentArgs.fromBundle(arguments).getJson();
|
||||
setHasOptionsMenu(true);
|
||||
Monster monster = MonsterImportHelper.fromJSON(json);
|
||||
mViewModel = new ViewModelProvider(this).get(MonsterImportViewModel.class);
|
||||
mViewModel.setMonster(monster);
|
||||
View root = inflater.inflate(R.layout.fragment_monster, container, false);
|
||||
mHolder = new ViewHolder(root);
|
||||
|
||||
mViewModel.getName().observe(getViewLifecycleOwner(), mHolder.name::setText);
|
||||
mViewModel.getMeta().observe(getViewLifecycleOwner(), mHolder.meta::setText);
|
||||
mViewModel.getArmorClass().observe(getViewLifecycleOwner(), armorText -> setupLabeledTextView(mHolder.armorClass, armorText, R.string.label_armor_class));
|
||||
mViewModel.getHitPoints().observe(getViewLifecycleOwner(), hitPoints -> setupLabeledTextView(mHolder.hitPoints, hitPoints, R.string.label_hit_points));
|
||||
mViewModel.getSpeed().observe(getViewLifecycleOwner(), speed -> setupLabeledTextView(mHolder.speed, speed, R.string.label_speed));
|
||||
mViewModel.getStrength().observe(getViewLifecycleOwner(), mHolder.strength::setText);
|
||||
mViewModel.getDexterity().observe(getViewLifecycleOwner(), mHolder.dexterity::setText);
|
||||
mViewModel.getConstitution().observe(getViewLifecycleOwner(), mHolder.constitution::setText);
|
||||
mViewModel.getIntelligence().observe(getViewLifecycleOwner(), mHolder.intelligence::setText);
|
||||
mViewModel.getWisdom().observe(getViewLifecycleOwner(), mHolder.wisdom::setText);
|
||||
mViewModel.getCharisma().observe(getViewLifecycleOwner(), mHolder.charisma::setText);
|
||||
mViewModel.getSavingThrows().observe(getViewLifecycleOwner(), savingThrows -> setupOptionalTextView(mHolder.savingThrows, savingThrows, R.string.label_saving_throws));
|
||||
mViewModel.getSkills().observe(getViewLifecycleOwner(), skills -> setupOptionalTextView(mHolder.skills, skills, R.string.label_skills));
|
||||
mViewModel.getDamageVulnerabilities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageVulnerabilities, damageTypes, R.string.label_damage_vulnerabilities));
|
||||
mViewModel.getDamageResistances().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageResistances, damageTypes, R.string.label_damage_resistances));
|
||||
mViewModel.getDamageImmunities().observe(getViewLifecycleOwner(), damageTypes -> setupOptionalTextView(mHolder.damageImmunities, damageTypes, R.string.label_damage_immunities));
|
||||
mViewModel.getConditionImmunities().observe(getViewLifecycleOwner(), conditionImmunities -> setupOptionalTextView(mHolder.conditionImmunities, conditionImmunities, R.string.label_condition_immunities));
|
||||
mViewModel.getSenses().observe(getViewLifecycleOwner(), senses -> setupOptionalTextView(mHolder.senses, senses, R.string.label_senses));
|
||||
mViewModel.getLanguages().observe(getViewLifecycleOwner(), languages -> setupOptionalTextView(mHolder.languages, languages, R.string.label_languages));
|
||||
mViewModel.getChallenge().observe(getViewLifecycleOwner(), challengeRating -> setupLabeledTextView(mHolder.challenge, challengeRating, R.string.label_challenge_rating));
|
||||
mViewModel.getAbilities().observe(getViewLifecycleOwner(), abilities -> setupTraitList(mHolder.abilities, abilities));
|
||||
mViewModel.getActions().observe(getViewLifecycleOwner(), actions -> setupTraitList(mHolder.actions, actions, mHolder.actions_label, mHolder.actions_divider));
|
||||
mViewModel.getReactions().observe(getViewLifecycleOwner(), reactions -> setupTraitList(mHolder.reactions, reactions, mHolder.reactions_label, mHolder.reactions_divider));
|
||||
mViewModel.getRegionalEffects().observe(getViewLifecycleOwner(), regionalEffects -> setupTraitList(mHolder.regionalEffects, regionalEffects, mHolder.regionalEffects_label, mHolder.regionalEffects_divider));
|
||||
mViewModel.getLairActions().observe(getViewLifecycleOwner(), lairActions -> setupTraitList(mHolder.lairActions, lairActions, mHolder.lairActions_label, mHolder.lairActions_divider));
|
||||
mViewModel.getLegendaryActions().observe(getViewLifecycleOwner(), legendaryActions -> setupTraitList(mHolder.legendaryActions, legendaryActions, mHolder.legendaryActions_label, mHolder.legendaryActions_divider));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupLabeledTextView(@NonNull TextView view, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
String fullText = String.format("<b>%s</b> %s", title, text);
|
||||
view.setText(Html.fromHtml(fullText));
|
||||
}
|
||||
|
||||
private void setupOptionalTextView(TextView root, String text, int titleId) {
|
||||
String title = getString(titleId);
|
||||
if (StringHelper.isNullOrEmpty(text)) {
|
||||
root.setVisibility(View.GONE);
|
||||
} else {
|
||||
root.setVisibility(View.VISIBLE);
|
||||
}
|
||||
Spanned formatted;
|
||||
if (StringHelper.isNullOrEmpty(title)) {
|
||||
formatted = Html.fromHtml(text);
|
||||
} else {
|
||||
formatted = Html.fromHtml(String.format("<b>%s</b> %s", title, text));
|
||||
}
|
||||
root.setText(formatted);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits) {
|
||||
setupTraitList(root, traits, null, null);
|
||||
}
|
||||
|
||||
private void setupTraitList(@NonNull LinearLayout root, @NonNull List<String> traits, View label, View divider) {
|
||||
int visibility = traits.size() > 0 ? View.VISIBLE : View.GONE;
|
||||
Context context = getContext();
|
||||
DisplayMetrics displayMetrics = null;
|
||||
if (context != null) {
|
||||
Resources resources = context.getResources();
|
||||
if (resources != null) {
|
||||
displayMetrics = resources.getDisplayMetrics();
|
||||
}
|
||||
}
|
||||
root.removeAllViews();
|
||||
for (String action : traits) {
|
||||
TextView tvAction = new TextView(getContext());
|
||||
// TODO: Handle multiline block quotes specially so they stay multiline.
|
||||
// TODO: Replace QuoteSpans in the result of fromHtml with something like this https://stackoverflow.com/questions/7717567/how-to-style-blockquotes-in-android-textviews to make them indent as expected
|
||||
tvAction.setText(Html.fromHtml(CommonMarkHelper.toHtml(action)));
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, displayMetrics);
|
||||
tvAction.setLayoutParams(layoutParams);
|
||||
root.addView(tvAction);
|
||||
}
|
||||
root.setVisibility(visibility);
|
||||
if (label != null) {
|
||||
label.setVisibility(visibility);
|
||||
}
|
||||
if (divider != null) {
|
||||
divider.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.import_monster, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.menu_action_import_monster) {
|
||||
Logger.logDebug("Menu Item Selected");
|
||||
Monster monster = mViewModel.getMonster();
|
||||
if (monster != null) {
|
||||
monster.id = UUID.randomUUID();
|
||||
MonsterCardsApplication application = (MonsterCardsApplication) getApplication();
|
||||
MonsterRepository repository = application.getMonsterRepository();
|
||||
repository.addMonster(monster).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Snackbar.make(
|
||||
mHolder.root,
|
||||
getString(R.string.snackbar_monster_created, monster.name),
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAction("Action", (_view) -> navigateToEditMonster(monster.id))
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) {
|
||||
Logger.logError("Error creating monster", e);
|
||||
Snackbar.make(mHolder.root, getString(R.string.snackbar_failed_to_create_monster), Snackbar.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Logger.logWTF("monsterId cannot be null.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void navigateToEditMonster(@NonNull UUID monsterId) {
|
||||
NavController navController = Navigation.findNavController(requireView());
|
||||
NavDirections action;
|
||||
action = MonsterImportFragmentDirections.actionMonsterImportFragmentToNavigationLibrary();
|
||||
navController.navigate(action);
|
||||
action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
|
||||
navController.navigate(action);
|
||||
action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
|
||||
navController.navigate(action);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
final View root;
|
||||
final TextView name;
|
||||
final TextView meta;
|
||||
final TextView armorClass;
|
||||
final TextView hitPoints;
|
||||
final TextView speed;
|
||||
final TextView strength;
|
||||
final TextView dexterity;
|
||||
final TextView constitution;
|
||||
final TextView intelligence;
|
||||
final TextView wisdom;
|
||||
final TextView charisma;
|
||||
final TextView savingThrows;
|
||||
final TextView skills;
|
||||
final TextView damageVulnerabilities;
|
||||
final TextView damageResistances;
|
||||
final TextView damageImmunities;
|
||||
final TextView conditionImmunities;
|
||||
final TextView senses;
|
||||
final TextView languages;
|
||||
final TextView challenge;
|
||||
final LinearLayout abilities;
|
||||
final LinearLayout actions;
|
||||
final TextView actions_label;
|
||||
final ImageView actions_divider;
|
||||
final LinearLayout reactions;
|
||||
final TextView reactions_label;
|
||||
final ImageView reactions_divider;
|
||||
final LinearLayout legendaryActions;
|
||||
final TextView legendaryActions_label;
|
||||
final ImageView legendaryActions_divider;
|
||||
final LinearLayout lairActions;
|
||||
final TextView lairActions_label;
|
||||
final ImageView lairActions_divider;
|
||||
final LinearLayout regionalEffects;
|
||||
final TextView regionalEffects_label;
|
||||
final ImageView regionalEffects_divider;
|
||||
|
||||
ViewHolder(@NonNull View root) {
|
||||
this.root = root;
|
||||
name = root.findViewById(R.id.name);
|
||||
meta = root.findViewById(R.id.meta);
|
||||
armorClass = root.findViewById(R.id.armorClass);
|
||||
hitPoints = root.findViewById(R.id.hitPoints);
|
||||
speed = root.findViewById(R.id.speed);
|
||||
strength = root.findViewById(R.id.strength);
|
||||
dexterity = root.findViewById(R.id.dexterity);
|
||||
constitution = root.findViewById(R.id.constitution);
|
||||
intelligence = root.findViewById(R.id.intelligence);
|
||||
wisdom = root.findViewById(R.id.wisdom);
|
||||
charisma = root.findViewById(R.id.charisma);
|
||||
savingThrows = root.findViewById(R.id.savingThrows);
|
||||
skills = root.findViewById(R.id.skills);
|
||||
damageVulnerabilities = root.findViewById(R.id.damageVulnerabilities);
|
||||
damageResistances = root.findViewById(R.id.damageResistances);
|
||||
damageImmunities = root.findViewById(R.id.damageImmunities);
|
||||
conditionImmunities = root.findViewById(R.id.conditionImmunities);
|
||||
senses = root.findViewById(R.id.senses);
|
||||
languages = root.findViewById(R.id.languages);
|
||||
challenge = root.findViewById(R.id.challenge);
|
||||
abilities = root.findViewById(R.id.abilities);
|
||||
actions = root.findViewById(R.id.actions);
|
||||
actions_divider = root.findViewById(R.id.actions_divider);
|
||||
actions_label = root.findViewById(R.id.actions_label);
|
||||
reactions = root.findViewById(R.id.reactions);
|
||||
reactions_divider = root.findViewById(R.id.reactions_divider);
|
||||
reactions_label = root.findViewById(R.id.reactions_label);
|
||||
legendaryActions = root.findViewById(R.id.legendaryActions);
|
||||
legendaryActions_divider = root.findViewById(R.id.legendaryActions_divider);
|
||||
legendaryActions_label = root.findViewById(R.id.legendaryActions_label);
|
||||
lairActions = root.findViewById(R.id.lairActions);
|
||||
lairActions_divider = root.findViewById(R.id.lairActions_divider);
|
||||
lairActions_label = root.findViewById(R.id.lairActions_label);
|
||||
regionalEffects = root.findViewById(R.id.regionalEffects);
|
||||
regionalEffects_divider = root.findViewById(R.id.regionalEffects_divider);
|
||||
regionalEffects_label = root.findViewById(R.id.regionalEffects_label);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package com.majinnaibu.monstercards.ui.monster;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class MonsterImportViewModel extends ViewModel {
|
||||
private final MutableLiveData<List<String>> mAbilities;
|
||||
private final MutableLiveData<List<String>> mActions;
|
||||
private final MutableLiveData<String> mArmorClass;
|
||||
private final MutableLiveData<String> mChallenge;
|
||||
private final MutableLiveData<String> mCharisma;
|
||||
private final MutableLiveData<String> mConditionImmunities;
|
||||
private final MutableLiveData<String> mConstitution;
|
||||
private final MutableLiveData<String> mDamageResistances;
|
||||
private final MutableLiveData<String> mDamageImmunities;
|
||||
private final MutableLiveData<String> mDamageVulnerabilities;
|
||||
private final MutableLiveData<String> mDexterity;
|
||||
private final MutableLiveData<String> mHitPoints;
|
||||
private final MutableLiveData<String> mIntelligence;
|
||||
private final MutableLiveData<List<String>> mLairActions;
|
||||
private final MutableLiveData<String> mLanguages;
|
||||
private final MutableLiveData<List<String>> mLegendaryActions;
|
||||
private final MutableLiveData<String> mMeta;
|
||||
private final MutableLiveData<String> mName;
|
||||
private final MutableLiveData<List<String>> mReactions;
|
||||
private final MutableLiveData<List<String>> mRegionalEffects;
|
||||
private final MutableLiveData<String> mSavingThrows;
|
||||
private final MutableLiveData<String> mSenses;
|
||||
private final MutableLiveData<String> mSkills;
|
||||
private final MutableLiveData<String> mSpeed;
|
||||
private final MutableLiveData<String> mStrength;
|
||||
private final MutableLiveData<String> mWisdom;
|
||||
private final MutableLiveData<UUID> mMonsterId;
|
||||
private Monster mMonster;
|
||||
|
||||
public MonsterImportViewModel() {
|
||||
mMonster = null;
|
||||
mAbilities = new MutableLiveData<>(new ArrayList<>());
|
||||
mActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mArmorClass = new MutableLiveData<>("");
|
||||
mChallenge = new MutableLiveData<>("");
|
||||
mCharisma = new MutableLiveData<>("");
|
||||
mConditionImmunities = new MutableLiveData<>("");
|
||||
mConstitution = new MutableLiveData<>("");
|
||||
mDamageImmunities = new MutableLiveData<>("");
|
||||
mDamageResistances = new MutableLiveData<>("");
|
||||
mDamageVulnerabilities = new MutableLiveData<>("");
|
||||
mDexterity = new MutableLiveData<>("");
|
||||
mHitPoints = new MutableLiveData<>("");
|
||||
mIntelligence = new MutableLiveData<>("");
|
||||
mLairActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mLanguages = new MutableLiveData<>("");
|
||||
mLegendaryActions = new MutableLiveData<>(new ArrayList<>());
|
||||
mMeta = new MutableLiveData<>("");
|
||||
mName = new MutableLiveData<>("");
|
||||
mReactions = new MutableLiveData<>(new ArrayList<>());
|
||||
mRegionalEffects = new MutableLiveData<>(new ArrayList<>());
|
||||
mSavingThrows = new MutableLiveData<>("");
|
||||
mSenses = new MutableLiveData<>("");
|
||||
mSkills = new MutableLiveData<>("");
|
||||
mSpeed = new MutableLiveData<>("");
|
||||
mStrength = new MutableLiveData<>("");
|
||||
mWisdom = new MutableLiveData<>("");
|
||||
mMonsterId = new MutableLiveData<>(UUID.fromString("00000000-0000-0000-0000-000000000000"));
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getAbilities() {
|
||||
return mAbilities;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getActions() {
|
||||
return mActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getReactions() {
|
||||
return mReactions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLegendaryActions() {
|
||||
return mLegendaryActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getLairActions() {
|
||||
return mLairActions;
|
||||
}
|
||||
|
||||
public LiveData<List<String>> getRegionalEffects() {
|
||||
return mRegionalEffects;
|
||||
}
|
||||
|
||||
public LiveData<String> getArmorClass() {
|
||||
return mArmorClass;
|
||||
}
|
||||
|
||||
public LiveData<String> getChallenge() {
|
||||
return mChallenge;
|
||||
}
|
||||
|
||||
public LiveData<String> getCharisma() {
|
||||
return mCharisma;
|
||||
}
|
||||
|
||||
public LiveData<String> getConditionImmunities() {
|
||||
return mConditionImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getConstitution() {
|
||||
return mConstitution;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageResistances() {
|
||||
return mDamageResistances;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageImmunities() {
|
||||
return mDamageImmunities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDamageVulnerabilities() {
|
||||
return mDamageVulnerabilities;
|
||||
}
|
||||
|
||||
public LiveData<String> getDexterity() {
|
||||
return mDexterity;
|
||||
}
|
||||
|
||||
public LiveData<String> getHitPoints() {
|
||||
return mHitPoints;
|
||||
}
|
||||
|
||||
public LiveData<String> getIntelligence() {
|
||||
return mIntelligence;
|
||||
}
|
||||
|
||||
public LiveData<String> getLanguages() {
|
||||
return mLanguages;
|
||||
}
|
||||
|
||||
public LiveData<String> getMeta() {
|
||||
return mMeta;
|
||||
}
|
||||
|
||||
public LiveData<String> getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public LiveData<String> getSavingThrows() {
|
||||
return mSavingThrows;
|
||||
}
|
||||
|
||||
public LiveData<String> getSenses() {
|
||||
return mSenses;
|
||||
}
|
||||
|
||||
public LiveData<String> getSkills() {
|
||||
return mSkills;
|
||||
}
|
||||
|
||||
public LiveData<String> getSpeed() {
|
||||
return mSpeed;
|
||||
}
|
||||
|
||||
public LiveData<String> getStrength() {
|
||||
return mStrength;
|
||||
}
|
||||
|
||||
public LiveData<String> getWisdom() {
|
||||
return mWisdom;
|
||||
}
|
||||
|
||||
public LiveData<UUID> getId() {
|
||||
return mMonsterId;
|
||||
}
|
||||
|
||||
public Monster getMonster() {
|
||||
return mMonster;
|
||||
}
|
||||
|
||||
public void setMonster(@NonNull Monster monster) {
|
||||
mMonster = monster;
|
||||
mAbilities.setValue(mMonster.getAbilityDescriptions());
|
||||
mActions.setValue(mMonster.getActionDescriptions());
|
||||
mArmorClass.setValue(mMonster.getArmorClass());
|
||||
mChallenge.setValue(mMonster.getChallengeRatingDescription());
|
||||
mCharisma.setValue(monster.getCharismaDescription());
|
||||
mConditionImmunities.setValue(mMonster.getConditionImmunitiesDescription());
|
||||
mConstitution.setValue(monster.getConstitutionDescription());
|
||||
mDamageImmunities.setValue(mMonster.getDamageImmunitiesDescription());
|
||||
mDamageResistances.setValue(mMonster.getDamageResistancesDescription());
|
||||
mDamageVulnerabilities.setValue(mMonster.getDamageVulnerabilitiesDescription());
|
||||
mDexterity.setValue(monster.getDexterityDescription());
|
||||
mHitPoints.setValue(mMonster.getHitPoints());
|
||||
mIntelligence.setValue(monster.getIntelligenceDescription());
|
||||
mLairActions.setValue(mMonster.getLairActionDescriptions());
|
||||
mLanguages.setValue(mMonster.getLanguagesDescription());
|
||||
mLegendaryActions.setValue(mMonster.getLegendaryActionDescriptions());
|
||||
mMeta.setValue(mMonster.getMeta());
|
||||
mMonsterId.setValue(mMonster.id);
|
||||
mName.setValue(mMonster.name);
|
||||
mReactions.setValue(monster.getReactionDescriptions());
|
||||
mRegionalEffects.setValue(monster.getRegionalActionDescriptions());
|
||||
mSavingThrows.setValue(monster.getSavingThrowsDescription());
|
||||
mSenses.setValue(monster.getSensesDescription());
|
||||
mSkills.setValue(monster.getSkillsDescription());
|
||||
mSpeed.setValue(mMonster.getSpeedText());
|
||||
mStrength.setValue(monster.getStrengthDescription());
|
||||
mWisdom.setValue(monster.getWisdomDescription());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.majinnaibu.monstercards.ui.search;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||
|
||||
public class SearchFragment extends MCFragment {
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.fragment_search, container, false);
|
||||
MonsterRepository repository = this.getMonsterRepository();
|
||||
SearchResultsRecyclerViewAdapter adapter = new SearchResultsRecyclerViewAdapter(repository, null);
|
||||
final RecyclerView recyclerView = root.findViewById(R.id.monster_list);
|
||||
assert recyclerView != null;
|
||||
setupRecyclerView(recyclerView, adapter);
|
||||
|
||||
final TextView textView = root.findViewById(R.id.search_query);
|
||||
textView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
adapter.doSearch(textView.getText().toString());
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(@NonNull RecyclerView recyclerView, @NonNull SearchResultsRecyclerViewAdapter adapter) {
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.majinnaibu.monstercards.ui.search;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
import com.majinnaibu.monstercards.models.Monster;
|
||||
import com.majinnaibu.monstercards.utils.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
|
||||
public class SearchResultsRecyclerViewAdapter extends RecyclerView.Adapter<SearchResultsRecyclerViewAdapter.ViewHolder> {
|
||||
private final MonsterRepository mRepository;
|
||||
private final ItemCallback mOnClickHandler;
|
||||
private String mSearchText;
|
||||
private List<Monster> mValues;
|
||||
private Disposable mSubscriptionHandler;
|
||||
|
||||
public SearchResultsRecyclerViewAdapter(MonsterRepository repository,
|
||||
ItemCallback onClick) {
|
||||
mRepository = repository;
|
||||
mSearchText = "";
|
||||
mValues = new ArrayList<>();
|
||||
mOnClickHandler = onClick;
|
||||
mSubscriptionHandler = null;
|
||||
|
||||
doSearch(mSearchText);
|
||||
}
|
||||
|
||||
public void doSearch(String searchText) {
|
||||
if (mSubscriptionHandler != null && !mSubscriptionHandler.isDisposed()) {
|
||||
mSubscriptionHandler.dispose();
|
||||
}
|
||||
mSearchText = searchText;
|
||||
Flowable<List<Monster>> foundMonsters = mRepository.searchMonsters(mSearchText);
|
||||
mSubscriptionHandler = foundMonsters.subscribe(monsters -> {
|
||||
mValues = monsters;
|
||||
notifyDataSetChanged();
|
||||
},
|
||||
throwable -> Logger.logError("Error performing search", throwable));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.monster_list_content, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||
Monster monster = mValues.get(position);
|
||||
holder.mContentView.setText(monster.name);
|
||||
holder.itemView.setTag(monster);
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (mOnClickHandler != null) {
|
||||
mOnClickHandler.onItem(monster);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mValues.size();
|
||||
}
|
||||
|
||||
public interface ItemCallback {
|
||||
void onItem(Monster monster);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView mContentView;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
mContentView = view.findViewById(R.id.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
public class ChangeTrackedViewModel extends ViewModel {
|
||||
private final MutableLiveData<Boolean> mHasChanges;
|
||||
|
||||
public ChangeTrackedViewModel() {
|
||||
mHasChanges = new MutableLiveData<>(false);
|
||||
}
|
||||
|
||||
public boolean hasChanges() {
|
||||
Boolean value = mHasChanges.getValue();
|
||||
return value != null && value;
|
||||
}
|
||||
|
||||
protected void makeDirty() {
|
||||
mHasChanges.setValue(true);
|
||||
}
|
||||
|
||||
protected void makeClean() {
|
||||
mHasChanges.setValue(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.majinnaibu.monstercards.MonsterCardsApplication;
|
||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||
|
||||
public class MCFragment extends Fragment {
|
||||
public MonsterCardsApplication getApplication() {
|
||||
return (MonsterCardsApplication) requireActivity().getApplication();
|
||||
}
|
||||
|
||||
protected MonsterRepository getMonsterRepository() {
|
||||
return this.getApplication().getMonsterRepository();
|
||||
}
|
||||
|
||||
public AppCompatActivity requireAppCompatActivity() {
|
||||
return (AppCompatActivity) requireActivity();
|
||||
}
|
||||
|
||||
public void setTitle(CharSequence title) {
|
||||
Activity activity = requireActivity();
|
||||
if (activity instanceof AppCompatActivity) {
|
||||
AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
|
||||
ActionBar supportActionBar = appCompatActivity.getSupportActionBar();
|
||||
if (supportActionBar != null) {
|
||||
supportActionBar.setTitle(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.majinnaibu.monstercards.ui.shared;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.majinnaibu.monstercards.R;
|
||||
|
||||
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
|
||||
|
||||
private final Drawable icon;
|
||||
private final ColorDrawable background;
|
||||
private final Paint clearPaint;
|
||||
private final OnSwipeCallback mOnDelete;
|
||||
private final OnMoveCallback mOnMove;
|
||||
private final Context mContext;
|
||||
|
||||
public SwipeToDeleteCallback(@NonNull Context context, OnSwipeCallback onDelete, OnMoveCallback onMove) {
|
||||
super(onMove == null ? 0 : ItemTouchHelper.UP | ItemTouchHelper.DOWN, onDelete == null ? 0 : ItemTouchHelper.LEFT);
|
||||
mOnDelete = onDelete;
|
||||
mOnMove = onMove;
|
||||
mContext = context;
|
||||
icon = ContextCompat.getDrawable(mContext, R.drawable.ic_delete_white_36);
|
||||
background = new ColorDrawable(context.getResources().getColor(R.color.red));
|
||||
clearPaint = new Paint();
|
||||
clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(
|
||||
@NonNull RecyclerView recyclerView,
|
||||
@NonNull RecyclerView.ViewHolder viewHolder,
|
||||
@NonNull RecyclerView.ViewHolder target
|
||||
) {
|
||||
if (mOnMove != null) {
|
||||
int from = viewHolder.getAdapterPosition();
|
||||
int to = target.getAdapterPosition();
|
||||
return mOnMove.onMove(from, to);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
|
||||
if (mOnDelete != null) {
|
||||
int position = viewHolder.getAdapterPosition();
|
||||
mOnDelete.onSwipe(position, direction);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
View itemView = viewHolder.itemView;
|
||||
int itemHeight = itemView.getBottom() - itemView.getTop();
|
||||
boolean isCancelled = dX == 0 && !isCurrentlyActive;
|
||||
|
||||
if (isCancelled) {
|
||||
c.drawRect(itemView.getRight() + dX, itemView.getTop(), itemView.getRight(), itemView.getBottom(), clearPaint);
|
||||
return;
|
||||
}
|
||||
// Draw the red delete background
|
||||
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
|
||||
background.draw(c);
|
||||
|
||||
// Calculate position of delete icon
|
||||
int iconHeight = icon.getIntrinsicHeight();
|
||||
int iconWidth = icon.getIntrinsicWidth();
|
||||
int iconTop = itemView.getTop() + (itemHeight - iconHeight) / 2;
|
||||
int iconMargin = (itemHeight - iconHeight) / 2;
|
||||
int iconLeft = itemView.getRight() - iconMargin - iconWidth;
|
||||
int iconRight = itemView.getRight() - iconMargin;
|
||||
int iconBottom = iconTop + iconHeight;
|
||||
|
||||
// Draw the icon
|
||||
icon.setBounds(iconLeft, iconTop, iconRight, iconBottom);
|
||||
icon.draw(c);
|
||||
|
||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
||||
}
|
||||
|
||||
public interface OnSwipeCallback {
|
||||
void onSwipe(int position, int direction);
|
||||
}
|
||||
|
||||
public interface OnMoveCallback {
|
||||
boolean onMove(int from, int to);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.majinnaibu.monstercards.utils;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ChangeTrackedLiveData<T> extends MutableLiveData<T> {
|
||||
private final OnValueChangedCallback<T> mOnValueChangedCallback;
|
||||
private final OnValueDirtiedCallback mOnValueDirtiedCallback;
|
||||
private T mReferenceValue;
|
||||
|
||||
public ChangeTrackedLiveData(T initialValue, OnValueChangedCallback<T> onValueChanged, OnValueDirtiedCallback onValueDirtied) {
|
||||
super(initialValue);
|
||||
mReferenceValue = initialValue;
|
||||
mOnValueChangedCallback = onValueChanged;
|
||||
if (mOnValueChangedCallback != null) {
|
||||
mOnValueChangedCallback.onValueChanged(initialValue);
|
||||
}
|
||||
mOnValueDirtiedCallback = onValueDirtied;
|
||||
}
|
||||
|
||||
public ChangeTrackedLiveData(T initialValue, OnValueChangedCallback<T> callback) {
|
||||
this(initialValue, callback, null);
|
||||
}
|
||||
|
||||
public ChangeTrackedLiveData(T initialValue, OnValueDirtiedCallback callback) {
|
||||
this(initialValue, null, callback);
|
||||
}
|
||||
|
||||
public void setReferenceValue(T referenceValue) {
|
||||
mReferenceValue = referenceValue;
|
||||
}
|
||||
|
||||
public void setCurrentValueAsReference() {
|
||||
mReferenceValue = getValue();
|
||||
}
|
||||
|
||||
public void resetValue(T value) {
|
||||
mReferenceValue = value;
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(T value) {
|
||||
if (!Objects.equals(getValue(), value)) {
|
||||
super.setValue(value);
|
||||
|
||||
if (mOnValueChangedCallback != null) {
|
||||
mOnValueChangedCallback.onValueChanged(value);
|
||||
}
|
||||
if (!Objects.equals(mReferenceValue, value) && mOnValueDirtiedCallback != null) {
|
||||
mOnValueDirtiedCallback.onValueDirtied();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnValueDirtiedCallback {
|
||||
void onValueDirtied();
|
||||
}
|
||||
|
||||
public interface OnValueChangedCallback<T> {
|
||||
void onValueChanged(T value);
|
||||
}
|
||||
}
|
||||
139
app/src/main/java/com/majinnaibu/monstercards/utils/Logger.java
Normal file
139
app/src/main/java/com/majinnaibu/monstercards/utils/Logger.java
Normal file
@@ -0,0 +1,139 @@
|
||||
package com.majinnaibu.monstercards.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class Logger {
|
||||
public static final String LOG_TAG = "MonsterCards";
|
||||
|
||||
public static void logUnimplementedMethod() {
|
||||
Exception ex = new Exception();
|
||||
StackTraceElement[] stackTrace = ex.getStackTrace();
|
||||
|
||||
String location = stackTrace[1].getClassName() + "." + stackTrace[1].getMethodName() + ":" + stackTrace[1].getLineNumber();
|
||||
logDebug("Method not yet implemented " + location);
|
||||
}
|
||||
|
||||
public static void logUnhandledError(Throwable e) {
|
||||
StackTraceElement stackTraceElement = e.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
logDebug("Exception was caught but not properly handled " + location);
|
||||
}
|
||||
|
||||
public static void logUnimplementedFeature(String featureDescription) {
|
||||
Exception ex = new Exception();
|
||||
StackTraceElement[] stackTrace = ex.getStackTrace();
|
||||
|
||||
String location = stackTrace[1].getClassName() + "." + stackTrace[1].getMethodName() + ":" + stackTrace[1].getLineNumber();
|
||||
logDebug("Feature not yet implemented " + featureDescription + " at " + location);
|
||||
}
|
||||
|
||||
//region WTF
|
||||
public static void logWTF(String message) {
|
||||
Log.wtf(LOG_TAG, message);
|
||||
}
|
||||
|
||||
public static void logWTF(Throwable throwable) {
|
||||
StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
String message = String.format("Unexpected error occurred at %s.", location);
|
||||
Log.wtf(LOG_TAG, message, throwable);
|
||||
}
|
||||
|
||||
public static void logWTF(String message, Throwable throwable) {
|
||||
Log.wtf(LOG_TAG, message, throwable);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Error
|
||||
public static void logError(String message) {
|
||||
Log.e(LOG_TAG, message);
|
||||
}
|
||||
|
||||
public static void logError(Throwable throwable) {
|
||||
StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
String message = String.format("Unexpected error occurred at %s.", location);
|
||||
Log.e(LOG_TAG, message, throwable);
|
||||
}
|
||||
|
||||
public static void logError(String message, Throwable throwable) {
|
||||
Log.e(LOG_TAG, message, throwable);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Warning
|
||||
public static void logWarning(String message) {
|
||||
Log.w(LOG_TAG, message);
|
||||
}
|
||||
|
||||
public static void logWarning(Throwable throwable) {
|
||||
StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
String message = String.format("Unexpected error occurred at %s.", location);
|
||||
Log.w(LOG_TAG, message, throwable);
|
||||
}
|
||||
|
||||
public static void logWarning(String message, Throwable throwable) {
|
||||
Log.w(LOG_TAG, message, throwable);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Info
|
||||
public static void logInfo(String message) {
|
||||
Log.i(LOG_TAG, message);
|
||||
}
|
||||
|
||||
public static void logInfo(Throwable throwable) {
|
||||
StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
String message = String.format("Unexpected error occurred at %s.", location);
|
||||
Log.i(LOG_TAG, message, throwable);
|
||||
}
|
||||
|
||||
public static void logInfo(String message, Throwable throwable) {
|
||||
Log.i(LOG_TAG, message, throwable);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Debug
|
||||
public static void logDebug(String message) {
|
||||
Log.d(LOG_TAG, message);
|
||||
}
|
||||
|
||||
public static void logDebug(Throwable throwable) {
|
||||
StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
String message = String.format("Unexpected error occurred at %s.", location);
|
||||
Log.d(LOG_TAG, message, throwable);
|
||||
}
|
||||
|
||||
public static void logDebug(String message, Throwable throwable) {
|
||||
Log.d(LOG_TAG, message, throwable);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Verbose
|
||||
public static void logVerbose(String message) {
|
||||
Log.v(LOG_TAG, message);
|
||||
}
|
||||
|
||||
public static void logVerbose(Throwable throwable) {
|
||||
StackTraceElement stackTraceElement = throwable.getStackTrace()[0];
|
||||
|
||||
String location = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber();
|
||||
String message = String.format("Unexpected error occurred at %s.", location);
|
||||
Log.v(LOG_TAG, message, throwable);
|
||||
}
|
||||
|
||||
public static void logVerbose(String message, Throwable throwable) {
|
||||
Log.v(LOG_TAG, message, throwable);
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.majinnaibu.monstercards.utils;
|
||||
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class TextChangedListener implements TextWatcher {
|
||||
|
||||
private final BeforeTextChangedCallback mBeforeTextChangedCallback;
|
||||
private final OnTextChangedCallback mOnTextChangedCallback;
|
||||
private final AfterTextChangedCallback mAfterTextChangedCallback;
|
||||
|
||||
public TextChangedListener(BeforeTextChangedCallback beforeTextChangedCallback, OnTextChangedCallback onTextChangedCallback, AfterTextChangedCallback afterTextChangedCallback) {
|
||||
mBeforeTextChangedCallback = beforeTextChangedCallback;
|
||||
mOnTextChangedCallback = onTextChangedCallback;
|
||||
mAfterTextChangedCallback = afterTextChangedCallback;
|
||||
}
|
||||
|
||||
public TextChangedListener(OnTextChangedCallback callback) {
|
||||
this(null, callback, null);
|
||||
}
|
||||
|
||||
public TextChangedListener(BeforeTextChangedCallback callback) {
|
||||
this(callback, null, null);
|
||||
}
|
||||
|
||||
public TextChangedListener(AfterTextChangedCallback callback) {
|
||||
this(null, null, callback);
|
||||
}
|
||||
|
||||
public TextChangedListener(BeforeTextChangedCallback beforeTextChangedCallback, OnTextChangedCallback onTextChangedCallback) {
|
||||
this(beforeTextChangedCallback, onTextChangedCallback, null);
|
||||
}
|
||||
|
||||
public TextChangedListener(BeforeTextChangedCallback beforeTextChangedCallback, AfterTextChangedCallback afterTextChangedCallback) {
|
||||
this(beforeTextChangedCallback, null, afterTextChangedCallback);
|
||||
}
|
||||
|
||||
public TextChangedListener(OnTextChangedCallback onTextChangedCallback, AfterTextChangedCallback afterTextChangedCallback) {
|
||||
this(null, onTextChangedCallback, afterTextChangedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
if (mBeforeTextChangedCallback != null) {
|
||||
mBeforeTextChangedCallback.beforeTextChanged(s, start, count, after);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (mOnTextChangedCallback != null) {
|
||||
mOnTextChangedCallback.onTextChanged(s, start, before, count);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (mAfterTextChangedCallback != null) {
|
||||
mAfterTextChangedCallback.afterTextChanged(s);
|
||||
}
|
||||
}
|
||||
|
||||
public interface BeforeTextChangedCallback {
|
||||
void beforeTextChanged(CharSequence s, int start, int count, int after);
|
||||
}
|
||||
|
||||
public interface OnTextChangedCallback {
|
||||
void onTextChanged(CharSequence s, int start, int before, int count);
|
||||
}
|
||||
|
||||
public interface AfterTextChangedCallback {
|
||||
void afterTextChanged(Editable s);
|
||||
}
|
||||
}
|
||||
5
app/src/main/res/color/radio_button_text.xml
Normal file
5
app/src/main/res/color/radio_button_text.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/colorOnPrimary" android:state_checked="true"/>
|
||||
<item android:color="@color/colorPrimary" android:state_checked="false"/>
|
||||
</selector>
|
||||
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
30
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_add_24.xml
Normal file
10
app/src/main/res/drawable/ic_add_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_chevron_right_24.xml
Normal file
10
app/src/main/res/drawable/ic_chevron_right_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/ic_collections_black_24dp.xml
Normal file
13
app/src/main/res/drawable/ic_collections_black_24dp.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,12l-2.5,-1.5L15,12L15,4h5v8z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_dashboard_black_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_dashboard_black_24dp.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_delete_white_36.xml
Normal file
5
app/src/main/res/drawable/ic_delete_white_36.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_edit_24.xml
Normal file
10
app/src/main/res/drawable/ic_edit_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/ic_library_add_24.xml
Normal file
5
app/src/main/res/drawable/ic_library_add_24.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM19,11h-4v4h-2v-4L9,11L9,9h4L13,5h2v4h4v2z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_library_black_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_library_black_24dp.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_search_black_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_search_black_24dp.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_section_divider.xml
Normal file
9
app/src/main/res/drawable/ic_section_divider.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="800.5dp"
|
||||
android:height="20.5dp"
|
||||
android:viewportWidth="800.5"
|
||||
android:viewportHeight="20.5">
|
||||
<path
|
||||
android:pathData="M0.5,0l-0.5,20.5l800.5,-20.5l-800,0z"
|
||||
android:fillColor="#9b2818"/>
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user