diff --git a/Habitica/build.gradle b/Habitica/build.gradle
index 2af6acfbea..3fe3b87aff 100644
--- a/Habitica/build.gradle
+++ b/Habitica/build.gradle
@@ -40,7 +40,7 @@ dependencies {
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
//App Compatibility and Material Design
implementation "androidx.appcompat:appcompat:$appcompat_version"
- implementation 'com.google.android.material:material:1.8.0'
+ implementation 'com.google.android.material:material:1.9.0'
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
implementation "androidx.preference:preference-ktx:$preferences_version"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
@@ -51,7 +51,7 @@ dependencies {
implementation('com.jaredrummler:android-device-names:2.1.1')
// IAP Handling / Verification
- implementation "com.android.billingclient:billing-ktx:5.2.0"
+ implementation "com.android.billingclient:billing-ktx:6.0.1"
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1@aar'
implementation("io.coil-kt:coil-compose:$coil_version")
@@ -73,7 +73,7 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
- debugImplementation 'androidx.fragment:fragment-testing:1.5.7'
+ debugImplementation 'androidx.fragment:fragment-testing:1.6.1'
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
debugImplementation "androidx.test:monitor:1.6.1"
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
@@ -95,7 +95,7 @@ dependencies {
implementation 'com.google.firebase:firebase-messaging-ktx'
implementation 'com.google.firebase:firebase-config-ktx'
implementation 'com.google.firebase:firebase-perf-ktx'
- implementation 'com.google.android.gms:play-services-ads:22.0.0'
+ implementation 'com.google.android.gms:play-services-ads:22.4.0'
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
@@ -106,16 +106,17 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
- implementation "androidx.fragment:fragment-ktx:1.5.7"
- implementation "androidx.paging:paging-runtime-ktx:3.1.1"
- implementation "androidx.paging:paging-compose:1.0.0-alpha19"
+ implementation "androidx.fragment:fragment-ktx:1.6.1"
+ implementation "androidx.paging:paging-runtime-ktx:$paging_version"
+ implementation "androidx.paging:paging-compose:$paging_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "com.google.accompanist:accompanist-themeadapter-material:$accompanist_version"
- implementation "androidx.compose.material3:material3:1.0.1"
+ implementation "androidx.compose.material3:material3:1.1.2"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
+ implementation 'com.google.android.play:core:1.10.3'
- implementation 'androidx.activity:activity-compose:1.7.1'
+ implementation 'androidx.activity:activity-compose:1.7.2'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.animation:animation:$compose_version"
@@ -142,6 +143,7 @@ android {
defaultConfig {
minSdkVersion min_sdk
+ compileSdk 34
applicationId "com.habitrpg.android.habitica"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "STORE", "\"google\""
@@ -168,7 +170,7 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion = "1.4.6"
+ kotlinCompilerExtensionVersion = "1.5.3"
}
signingConfigs {
diff --git a/Habitica/res/drawable-hdpi/checkmark_small.png b/Habitica/res/drawable-hdpi/checkmark_small.png
new file mode 100644
index 0000000000..c23aec1271
Binary files /dev/null and b/Habitica/res/drawable-hdpi/checkmark_small.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_boss_health.png b/Habitica/res/drawable-hdpi/icon_boss_health.png
new file mode 100644
index 0000000000..8c42c61a5d
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_boss_health.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_boss_rage.png b/Habitica/res/drawable-hdpi/icon_boss_rage.png
new file mode 100644
index 0000000000..a0527dec0a
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_boss_rage.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_eggs.png b/Habitica/res/drawable-hdpi/icon_eggs.png
new file mode 100644
index 0000000000..b2fd0cb575
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_eggs.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_food.png b/Habitica/res/drawable-hdpi/icon_food.png
new file mode 100644
index 0000000000..d4ad2f7e55
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_food.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_hatchingpotions.png b/Habitica/res/drawable-hdpi/icon_hatchingpotions.png
new file mode 100644
index 0000000000..cac75518b2
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_hatchingpotions.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_lock.png b/Habitica/res/drawable-hdpi/icon_lock.png
new file mode 100644
index 0000000000..e83bc86363
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_lock.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_quests.png b/Habitica/res/drawable-hdpi/icon_quests.png
new file mode 100644
index 0000000000..f8dfab6ab1
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_quests.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_shops.png b/Habitica/res/drawable-hdpi/icon_shops.png
new file mode 100644
index 0000000000..26cd85f342
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_shops.png differ
diff --git a/Habitica/res/drawable-hdpi/icon_special.png b/Habitica/res/drawable-hdpi/icon_special.png
new file mode 100644
index 0000000000..98021f474f
Binary files /dev/null and b/Habitica/res/drawable-hdpi/icon_special.png differ
diff --git a/Habitica/res/drawable-hdpi/indicator_subscribe.png b/Habitica/res/drawable-hdpi/indicator_subscribe.png
new file mode 100644
index 0000000000..9ffc0beeee
Binary files /dev/null and b/Habitica/res/drawable-hdpi/indicator_subscribe.png differ
diff --git a/Habitica/res/drawable-hdpi/sub_benefits_armoire.png b/Habitica/res/drawable-hdpi/sub_benefits_armoire.png
new file mode 100644
index 0000000000..2b990ebc0b
Binary files /dev/null and b/Habitica/res/drawable-hdpi/sub_benefits_armoire.png differ
diff --git a/Habitica/res/drawable-mdpi/checkmark_small.png b/Habitica/res/drawable-mdpi/checkmark_small.png
new file mode 100644
index 0000000000..8365cc6352
Binary files /dev/null and b/Habitica/res/drawable-mdpi/checkmark_small.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_base.png b/Habitica/res/drawable-mdpi/feed_base.png
new file mode 100644
index 0000000000..da6110c19e
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_base.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_blue.png b/Habitica/res/drawable-mdpi/feed_blue.png
new file mode 100644
index 0000000000..59450f8909
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_blue.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_desert.png b/Habitica/res/drawable-mdpi/feed_desert.png
new file mode 100644
index 0000000000..218fcfd4f0
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_desert.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_golden.png b/Habitica/res/drawable-mdpi/feed_golden.png
new file mode 100644
index 0000000000..b3d7856be3
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_golden.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_pink.png b/Habitica/res/drawable-mdpi/feed_pink.png
new file mode 100644
index 0000000000..03ed882ce2
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_pink.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_red.png b/Habitica/res/drawable-mdpi/feed_red.png
new file mode 100644
index 0000000000..322ca2a3f6
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_red.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_saddle.png b/Habitica/res/drawable-mdpi/feed_saddle.png
new file mode 100644
index 0000000000..687142fb12
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_saddle.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_shade.png b/Habitica/res/drawable-mdpi/feed_shade.png
new file mode 100644
index 0000000000..c73a90c518
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_shade.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_skeleton.png b/Habitica/res/drawable-mdpi/feed_skeleton.png
new file mode 100644
index 0000000000..2906d3b38a
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_skeleton.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_white.png b/Habitica/res/drawable-mdpi/feed_white.png
new file mode 100644
index 0000000000..0465dab6da
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_white.png differ
diff --git a/Habitica/res/drawable-mdpi/feed_zombie.png b/Habitica/res/drawable-mdpi/feed_zombie.png
new file mode 100644
index 0000000000..0f21364fb2
Binary files /dev/null and b/Habitica/res/drawable-mdpi/feed_zombie.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_boss_health.png b/Habitica/res/drawable-mdpi/icon_boss_health.png
new file mode 100644
index 0000000000..764c499f80
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_boss_health.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_boss_rage.png b/Habitica/res/drawable-mdpi/icon_boss_rage.png
new file mode 100644
index 0000000000..9dd9fe7676
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_boss_rage.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_eggs.png b/Habitica/res/drawable-mdpi/icon_eggs.png
new file mode 100644
index 0000000000..1a3a58ebb3
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_eggs.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_food.png b/Habitica/res/drawable-mdpi/icon_food.png
new file mode 100644
index 0000000000..cd8b8fc38b
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_food.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_hatchingpotions.png b/Habitica/res/drawable-mdpi/icon_hatchingpotions.png
new file mode 100644
index 0000000000..1483a1693e
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_hatchingpotions.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_lock.png b/Habitica/res/drawable-mdpi/icon_lock.png
new file mode 100644
index 0000000000..181b86336f
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_lock.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_quests.png b/Habitica/res/drawable-mdpi/icon_quests.png
new file mode 100644
index 0000000000..9e18dae9c9
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_quests.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_shops.png b/Habitica/res/drawable-mdpi/icon_shops.png
new file mode 100644
index 0000000000..7ae1882c11
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_shops.png differ
diff --git a/Habitica/res/drawable-mdpi/icon_special.png b/Habitica/res/drawable-mdpi/icon_special.png
new file mode 100644
index 0000000000..191f7ecdd2
Binary files /dev/null and b/Habitica/res/drawable-mdpi/icon_special.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_april.png b/Habitica/res/drawable-mdpi/stable_tile_april.png
new file mode 100644
index 0000000000..d5cd1e0217
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_april.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_august.png b/Habitica/res/drawable-mdpi/stable_tile_august.png
new file mode 100644
index 0000000000..29afec1402
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_august.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_december.png b/Habitica/res/drawable-mdpi/stable_tile_december.png
new file mode 100644
index 0000000000..cf8678da00
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_december.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_february.png b/Habitica/res/drawable-mdpi/stable_tile_february.png
new file mode 100644
index 0000000000..bfaf85ede2
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_february.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_janurary.png b/Habitica/res/drawable-mdpi/stable_tile_janurary.png
new file mode 100644
index 0000000000..6a84632c6e
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_janurary.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_july.png b/Habitica/res/drawable-mdpi/stable_tile_july.png
new file mode 100644
index 0000000000..371d7e5e4b
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_july.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_june.png b/Habitica/res/drawable-mdpi/stable_tile_june.png
new file mode 100644
index 0000000000..aaea748e00
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_june.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_march.png b/Habitica/res/drawable-mdpi/stable_tile_march.png
new file mode 100644
index 0000000000..393d6a6656
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_march.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_may.png b/Habitica/res/drawable-mdpi/stable_tile_may.png
new file mode 100644
index 0000000000..bb017c40bf
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_may.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_november.png b/Habitica/res/drawable-mdpi/stable_tile_november.png
new file mode 100644
index 0000000000..565958f4e4
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_november.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_october.png b/Habitica/res/drawable-mdpi/stable_tile_october.png
new file mode 100644
index 0000000000..7841588a5a
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_october.png differ
diff --git a/Habitica/res/drawable-mdpi/stable_tile_september.png b/Habitica/res/drawable-mdpi/stable_tile_september.png
new file mode 100644
index 0000000000..e6faa878b7
Binary files /dev/null and b/Habitica/res/drawable-mdpi/stable_tile_september.png differ
diff --git a/Habitica/res/drawable-mdpi/sub_benefits_armoire.png b/Habitica/res/drawable-mdpi/sub_benefits_armoire.png
new file mode 100644
index 0000000000..fbb5705bfd
Binary files /dev/null and b/Habitica/res/drawable-mdpi/sub_benefits_armoire.png differ
diff --git a/Habitica/res/drawable-mdpi/sub_benefits_faint.png b/Habitica/res/drawable-mdpi/sub_benefits_faint.png
new file mode 100644
index 0000000000..f3271dfdb9
Binary files /dev/null and b/Habitica/res/drawable-mdpi/sub_benefits_faint.png differ
diff --git a/Habitica/res/drawable-night/indicator_subscribe.png b/Habitica/res/drawable-night/indicator_subscribe.png
new file mode 100644
index 0000000000..9ffc0beeee
Binary files /dev/null and b/Habitica/res/drawable-night/indicator_subscribe.png differ
diff --git a/Habitica/res/drawable-xhdpi/checkmark_small.png b/Habitica/res/drawable-xhdpi/checkmark_small.png
new file mode 100644
index 0000000000..4545f631f0
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/checkmark_small.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_boss_health.png b/Habitica/res/drawable-xhdpi/icon_boss_health.png
new file mode 100644
index 0000000000..bbd8a71a54
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_boss_health.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_boss_rage.png b/Habitica/res/drawable-xhdpi/icon_boss_rage.png
new file mode 100644
index 0000000000..f7662ba116
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_boss_rage.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_eggs.png b/Habitica/res/drawable-xhdpi/icon_eggs.png
new file mode 100644
index 0000000000..9eef23c5bc
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_eggs.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_food.png b/Habitica/res/drawable-xhdpi/icon_food.png
new file mode 100644
index 0000000000..21850ec289
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_food.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_hatchingpotions.png b/Habitica/res/drawable-xhdpi/icon_hatchingpotions.png
new file mode 100644
index 0000000000..6bf2d0e5cf
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_hatchingpotions.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_lock.png b/Habitica/res/drawable-xhdpi/icon_lock.png
new file mode 100644
index 0000000000..70e12a9382
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_lock.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_quests.png b/Habitica/res/drawable-xhdpi/icon_quests.png
new file mode 100644
index 0000000000..e09b5fd395
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_quests.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_shops.png b/Habitica/res/drawable-xhdpi/icon_shops.png
new file mode 100644
index 0000000000..c9cc3e58ae
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_shops.png differ
diff --git a/Habitica/res/drawable-xhdpi/icon_special.png b/Habitica/res/drawable-xhdpi/icon_special.png
new file mode 100644
index 0000000000..9f4b7f26c2
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/icon_special.png differ
diff --git a/Habitica/res/drawable-xhdpi/indicator_subscribe.png b/Habitica/res/drawable-xhdpi/indicator_subscribe.png
new file mode 100644
index 0000000000..84bf95ac73
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/indicator_subscribe.png differ
diff --git a/Habitica/res/drawable-xhdpi/sub_benefits_armoire.png b/Habitica/res/drawable-xhdpi/sub_benefits_armoire.png
new file mode 100644
index 0000000000..d54695a623
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/sub_benefits_armoire.png differ
diff --git a/Habitica/res/drawable-xhdpi/sub_benefits_faint.png b/Habitica/res/drawable-xhdpi/sub_benefits_faint.png
new file mode 100644
index 0000000000..0a588745b1
Binary files /dev/null and b/Habitica/res/drawable-xhdpi/sub_benefits_faint.png differ
diff --git a/Habitica/res/drawable-xxhdpi/checkmark_small.png b/Habitica/res/drawable-xxhdpi/checkmark_small.png
new file mode 100644
index 0000000000..d35a15dac6
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/checkmark_small.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_boss_health.png b/Habitica/res/drawable-xxhdpi/icon_boss_health.png
new file mode 100644
index 0000000000..54f7c4919a
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_boss_health.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_boss_rage.png b/Habitica/res/drawable-xxhdpi/icon_boss_rage.png
new file mode 100644
index 0000000000..cdc741ab20
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_boss_rage.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_eggs.png b/Habitica/res/drawable-xxhdpi/icon_eggs.png
new file mode 100644
index 0000000000..15a83f6df2
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_eggs.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_food.png b/Habitica/res/drawable-xxhdpi/icon_food.png
new file mode 100644
index 0000000000..1100d90c02
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_food.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_hatchingpotions.png b/Habitica/res/drawable-xxhdpi/icon_hatchingpotions.png
new file mode 100644
index 0000000000..f0527ce5bd
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_hatchingpotions.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_lock.png b/Habitica/res/drawable-xxhdpi/icon_lock.png
new file mode 100644
index 0000000000..7a694af4bf
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_lock.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_quests.png b/Habitica/res/drawable-xxhdpi/icon_quests.png
new file mode 100644
index 0000000000..a17600b059
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_quests.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_shops.png b/Habitica/res/drawable-xxhdpi/icon_shops.png
new file mode 100644
index 0000000000..e510983438
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_shops.png differ
diff --git a/Habitica/res/drawable-xxhdpi/icon_special.png b/Habitica/res/drawable-xxhdpi/icon_special.png
new file mode 100644
index 0000000000..5802414e6b
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/icon_special.png differ
diff --git a/Habitica/res/drawable-xxhdpi/indicator_subscribe.png b/Habitica/res/drawable-xxhdpi/indicator_subscribe.png
new file mode 100644
index 0000000000..cb4d663b6d
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/indicator_subscribe.png differ
diff --git a/Habitica/res/drawable-xxhdpi/sub_benefits_armoire.png b/Habitica/res/drawable-xxhdpi/sub_benefits_armoire.png
new file mode 100644
index 0000000000..eeabbecfb7
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/sub_benefits_armoire.png differ
diff --git a/Habitica/res/drawable-xxhdpi/sub_benefits_faint.png b/Habitica/res/drawable-xxhdpi/sub_benefits_faint.png
new file mode 100644
index 0000000000..8e126c2621
Binary files /dev/null and b/Habitica/res/drawable-xxhdpi/sub_benefits_faint.png differ
diff --git a/Habitica/res/drawable/ad_button_background.xml b/Habitica/res/drawable/ad_button_background.xml
index 87c8b59d10..5186b00329 100644
--- a/Habitica/res/drawable/ad_button_background.xml
+++ b/Habitica/res/drawable/ad_button_background.xml
@@ -7,7 +7,7 @@
android:angle="0"
android:startColor="@color/green_100"
android:endColor="@color/green_500" />
-
+
-
+
diff --git a/Habitica/res/drawable/ad_button_background_content.xml b/Habitica/res/drawable/ad_button_background_content.xml
index a53d99acac..ab553aad05 100644
--- a/Habitica/res/drawable/ad_button_background_content.xml
+++ b/Habitica/res/drawable/ad_button_background_content.xml
@@ -7,7 +7,7 @@
android:angle="0"
android:startColor="@color/green_100"
android:endColor="@color/green_500" />
-
+
-
+
diff --git a/Habitica/res/drawable/armoire_background_full.xml b/Habitica/res/drawable/armoire_background_full.xml
new file mode 100644
index 0000000000..bb9de1b2f0
--- /dev/null
+++ b/Habitica/res/drawable/armoire_background_full.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/avatar_customization_subcategory_bg.xml b/Habitica/res/drawable/avatar_customization_subcategory_bg.xml
index 46989087c0..1eaeca2670 100644
--- a/Habitica/res/drawable/avatar_customization_subcategory_bg.xml
+++ b/Habitica/res/drawable/avatar_customization_subcategory_bg.xml
@@ -2,10 +2,10 @@
-
+
-
\ No newline at end of file
+
diff --git a/Habitica/res/drawable/boss_health_bg.xml b/Habitica/res/drawable/boss_health_bg.xml
index 44bb6beb0b..f87ddaf71f 100644
--- a/Habitica/res/drawable/boss_health_bg.xml
+++ b/Habitica/res/drawable/boss_health_bg.xml
@@ -1,5 +1,5 @@
-
-
\ No newline at end of file
+
+
diff --git a/Habitica/res/drawable/confetti_subs.xml b/Habitica/res/drawable/confetti_subs.xml
new file mode 100644
index 0000000000..e148b40163
--- /dev/null
+++ b/Habitica/res/drawable/confetti_subs.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/Habitica/res/drawable/item_indicator_subscribe.xml b/Habitica/res/drawable/item_indicator_subscribe.xml
new file mode 100644
index 0000000000..638af238a0
--- /dev/null
+++ b/Habitica/res/drawable/item_indicator_subscribe.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/pill_bg_green.xml b/Habitica/res/drawable/pill_bg_green.xml
index ed85dcfaa1..6d0e913807 100644
--- a/Habitica/res/drawable/pill_bg_green.xml
+++ b/Habitica/res/drawable/pill_bg_green.xml
@@ -1,6 +1,6 @@
-
+
-
\ No newline at end of file
+
diff --git a/Habitica/res/drawable/quest_collection_bg.xml b/Habitica/res/drawable/quest_collection_bg.xml
index 8fc079ea9a..97817b1283 100644
--- a/Habitica/res/drawable/quest_collection_bg.xml
+++ b/Habitica/res/drawable/quest_collection_bg.xml
@@ -1,5 +1,5 @@
-
-
\ No newline at end of file
+
+
diff --git a/Habitica/res/drawable/quest_difficulty_bg.xml b/Habitica/res/drawable/quest_difficulty_bg.xml
index c59d182caf..7f170b3c23 100644
--- a/Habitica/res/drawable/quest_difficulty_bg.xml
+++ b/Habitica/res/drawable/quest_difficulty_bg.xml
@@ -2,6 +2,6 @@
-
+
-
\ No newline at end of file
+
diff --git a/Habitica/res/drawable/shop_locked.xml b/Habitica/res/drawable/shop_locked.xml
new file mode 100644
index 0000000000..59cfd6f02e
--- /dev/null
+++ b/Habitica/res/drawable/shop_locked.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/snackbar_background_black.xml b/Habitica/res/drawable/snackbar_background_black.xml
new file mode 100644
index 0000000000..6fa29e2994
--- /dev/null
+++ b/Habitica/res/drawable/snackbar_background_black.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/drawable/sub_perk_bg.xml b/Habitica/res/drawable/sub_perk_bg.xml
new file mode 100644
index 0000000000..a62e9ef6f5
--- /dev/null
+++ b/Habitica/res/drawable/sub_perk_bg.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Habitica/res/drawable/subscribe_incentive_bg.xml b/Habitica/res/drawable/subscribe_incentive_bg.xml
new file mode 100644
index 0000000000..7fc46c5f00
--- /dev/null
+++ b/Habitica/res/drawable/subscribe_incentive_bg.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Habitica/res/drawable/subscribe_incentive_bg_topround.xml b/Habitica/res/drawable/subscribe_incentive_bg_topround.xml
new file mode 100644
index 0000000000..281d577109
--- /dev/null
+++ b/Habitica/res/drawable/subscribe_incentive_bg_topround.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Habitica/res/drawable/subscriber_benefit_button_bg.xml b/Habitica/res/drawable/subscriber_benefit_button_bg.xml
new file mode 100644
index 0000000000..76e4c55b5a
--- /dev/null
+++ b/Habitica/res/drawable/subscriber_benefit_button_bg.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/subscriber_benefit_snackbar_bg.xml b/Habitica/res/drawable/subscriber_benefit_snackbar_bg.xml
new file mode 100644
index 0000000000..2c2745de8e
--- /dev/null
+++ b/Habitica/res/drawable/subscriber_benefit_snackbar_bg.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/subscription_banner_image_left.xml b/Habitica/res/drawable/subscription_banner_image_left.xml
new file mode 100644
index 0000000000..0261df640d
--- /dev/null
+++ b/Habitica/res/drawable/subscription_banner_image_left.xml
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/subscription_banner_image_right.xml b/Habitica/res/drawable/subscription_banner_image_right.xml
new file mode 100644
index 0000000000..d13de7c95a
--- /dev/null
+++ b/Habitica/res/drawable/subscription_banner_image_right.xml
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/drawable/subscription_type_box_bg.xml b/Habitica/res/drawable/subscription_type_box_bg.xml
new file mode 100644
index 0000000000..147c40f4a5
--- /dev/null
+++ b/Habitica/res/drawable/subscription_type_box_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/activity_armoire.xml b/Habitica/res/layout/activity_armoire.xml
index dc99484960..2ab5f53a07 100644
--- a/Habitica/res/layout/activity_armoire.xml
+++ b/Habitica/res/layout/activity_armoire.xml
@@ -1,172 +1,298 @@
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent" />
+
-
-
+
+
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
-
-
-
+
+
+ android:background="@drawable/armoire_background_full"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:paddingTop="64dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
\ No newline at end of file
+
diff --git a/Habitica/res/layout/activity_class_selection.xml b/Habitica/res/layout/activity_class_selection.xml
index 72ebd04073..81ae8a0b6d 100644
--- a/Habitica/res/layout/activity_class_selection.xml
+++ b/Habitica/res/layout/activity_class_selection.xml
@@ -171,7 +171,7 @@
android:layout_height="wrap_content"
tools:text="@string/healer_description"
android:gravity="center_horizontal"
- style="@style/Body2"
+ style="@style/Body4"
android:lineSpacingExtra="4sp"/>
+ xmlns:tools="http://schemas.android.com/tools">
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+ android:layout_height="400dp"
+ android:layout_gravity="bottom"
+ android:paddingBottom="20dp"/>
diff --git a/Habitica/res/layout/activity_fixcharacter.xml b/Habitica/res/layout/activity_fixcharacter.xml
index e995137eb2..b81e46b603 100644
--- a/Habitica/res/layout/activity_fixcharacter.xml
+++ b/Habitica/res/layout/activity_fixcharacter.xml
@@ -55,13 +55,19 @@
android:layout_width="0dp"
android:layout_weight="1"
android:background="@drawable/layout_rounded_bg_window"
+ app:boxBackgroundMode="none"
android:hint="@string/health"
- android:paddingStart="16dp">
+ android:paddingStart="16dp"
+ android:paddingEnd="0dp"
+ android:paddingBottom="0dp"
+ android:paddingTop="8dp">
-
+ android:paddingStart="16dp"
+ android:paddingEnd="0dp"
+ android:paddingBottom="0dp"
+ android:paddingTop="8dp">
-
+ android:paddingStart="16dp"
+ android:paddingEnd="0dp"
+ android:paddingBottom="0dp"
+ android:paddingTop="8dp">
-
+ android:paddingStart="16dp"
+ android:paddingEnd="0dp"
+ android:paddingBottom="0dp"
+ android:paddingTop="8dp">
-
+ android:paddingStart="16dp"
+ android:paddingEnd="0dp"
+ android:paddingBottom="0dp"
+ android:paddingTop="8dp">
-
+ android:paddingStart="16dp"
+ android:paddingEnd="0dp"
+ android:paddingBottom="0dp"
+ android:paddingTop="8dp">
-
-
\ No newline at end of file
+
diff --git a/Habitica/res/layout/activity_report_message.xml b/Habitica/res/layout/activity_report_message.xml
index f16c97117b..ab39fe2db2 100644
--- a/Habitica/res/layout/activity_report_message.xml
+++ b/Habitica/res/layout/activity_report_message.xml
@@ -102,7 +102,7 @@
android:theme="@style/TextInputLayoutAppearanceTheme"
style="@style/TextInputLayoutAppearance"
android:layout_marginTop="@dimen/spacing_large"
- android:hint="@string/reason_for_report"
+ android:hint="@string/report_message_hint"
android:layout_marginBottom="@dimen/spacing_large">
+ style="@style/HabiticaButton.Maroon">
\ No newline at end of file
diff --git a/Habitica/res/layout/dialog_purchase_content_quest.xml b/Habitica/res/layout/dialog_purchase_content_quest.xml
index 3a4515b82d..90c8d5c95a 100644
--- a/Habitica/res/layout/dialog_purchase_content_quest.xml
+++ b/Habitica/res/layout/dialog_purchase_content_quest.xml
@@ -98,6 +98,8 @@
tools:text="List of items"
android:textColor="@color/white"
style="@style/Body2"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
/>
-
\ No newline at end of file
+
diff --git a/Habitica/res/layout/drawer_main.xml b/Habitica/res/layout/drawer_main.xml
index d10d097b59..22ec180709 100644
--- a/Habitica/res/layout/drawer_main.xml
+++ b/Habitica/res/layout/drawer_main.xml
@@ -18,17 +18,19 @@
android:background="?colorPrimaryOffset"
android:baselineAligned="false">
-
+ android:background="@drawable/rounded_avatar_bg"
+ android:clipToPadding="true"
+ android:clipChildren="true">
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Habitica/res/layout/fragment_bottomsheet_subscription.xml b/Habitica/res/layout/fragment_bottomsheet_subscription.xml
new file mode 100644
index 0000000000..f2ce1e397d
--- /dev/null
+++ b/Habitica/res/layout/fragment_bottomsheet_subscription.xml
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/layout/fragment_items.xml b/Habitica/res/layout/fragment_items.xml
index e09f6dd3dc..7f1ba89df3 100644
--- a/Habitica/res/layout/fragment_items.xml
+++ b/Habitica/res/layout/fragment_items.xml
@@ -26,19 +26,4 @@
android:scrollbarThumbVertical="@color/scrollbarThumb"
android:scrollbars="vertical" />
-
-
diff --git a/Habitica/res/layout/fragment_items_dialog.xml b/Habitica/res/layout/fragment_items_dialog.xml
index 5a3d0c5f83..9b40f37c1c 100644
--- a/Habitica/res/layout/fragment_items_dialog.xml
+++ b/Habitica/res/layout/fragment_items_dialog.xml
@@ -24,20 +24,5 @@
android:scrollbarThumbVertical="@color/scrollbarThumb"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:scrollbars="vertical" />
-
-
diff --git a/Habitica/res/layout/fragment_party_detail.xml b/Habitica/res/layout/fragment_party_detail.xml
index b908fdf46a..df7666e724 100644
--- a/Habitica/res/layout/fragment_party_detail.xml
+++ b/Habitica/res/layout/fragment_party_detail.xml
@@ -61,7 +61,8 @@
android:padding="@dimen/spacing_medium"
android:gravity="center_vertical"
android:layout_marginBottom="@dimen/spacing_medium"
- android:visibility="gone">
+ android:visibility="gone"
+ tools:visibility="visible">
+ style="@style/Body1"
+ android:textColor="@color/text_ternary" />
-
+ android:layout_marginBottom="@dimen/spacing_medium"
+ android:clipToPadding="true"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:orientation="vertical">
-
-
+
+
+
+
+
+ android:layout_height="wrap_content"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:text="@string/quest_mechanics"
+ android:textColor="@color/brand_button" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Habitica/res/layout/fragment_subscription.xml b/Habitica/res/layout/fragment_subscription.xml
index d2d47c673d..7520d5bb22 100644
--- a/Habitica/res/layout/fragment_subscription.xml
+++ b/Habitica/res/layout/fragment_subscription.xml
@@ -1,413 +1,291 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:scrollbarSize="3dp"
+ android:scrollbarThumbVertical="@color/scrollbarThumb"
+ android:scrollbars="vertical">
-
-
+ android:orientation="vertical">
-
-
-
-
-
+ android:visibility="gone" />
-
+ android:layout_height="80dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginEnd="10dp"
+ android:layout_marginBottom="8dp"
+ android:background="@drawable/g1g1_box"
+ android:clickable="true"
+ android:clipChildren="true"
+ android:clipToOutline="true"
+ android:clipToPadding="true"
+ android:visibility="gone"
+ tools:visibility="visible">
+
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentBottom="false"
+ android:scaleType="center" />
+
+ android:id="@+id/promo_banner_right_image"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true"
+ android:scaleType="center" />
+
-
-
+
+
-
-
-
-
-
+ android:layout_marginBottom="6dp" />
+
+ android:layout_marginStart="84dp"
+ android:layout_marginEnd="84dp"
+ android:layout_marginBottom="6dp"
+ android:gravity="center"
+ android:textColor="@color/white"
+ android:visibility="gone" />
+
+ android:layout_height="wrap_content" />
-
-
-
-
-
-
-
-
+ android:layout_centerInParent="true"
+ android:layout_marginStart="60dp"
+ android:layout_marginEnd="60dp"
+ android:layout_marginBottom="12dp"
+ android:fontFamily="@string/font_family_medium"
+ android:gravity="center_horizontal"
+ android:textColor="@color/white"
+ android:textSize="16sp" />
+
+
+ android:orientation="vertical"
+ android:padding="20dp">
+
-
-
-
-
-
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="20dp"
+ android:src="@drawable/subscribe_header" />
+
-
+
-
+
+
-
-
+
+
+
-
+ android:paddingTop="50dp"
+ android:paddingBottom="10dp"
+ android:text="@string/no_billing_subscriptions"
+ android:visibility="gone" />
-
+ android:layout_gravity="center"
+ android:layout_marginBottom="50dp"
+ android:text="@string/visit_habitica_website"
+ android:visibility="gone" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+ android:layout_marginStart="@dimen/spacing_large"
+ android:layout_marginEnd="@dimen/spacing_large"
+ android:gravity="center_horizontal"
+ android:text="@string/subscribe_gift_description"
+ android:textColor="@color/text_quad" />
+ android:textColor="@color/text_brand_neon" />
-
-
-
-
-
-
+ android:background="?attr/colorWindowBackground">
+
+
+
-
-
\ No newline at end of file
+
+
diff --git a/Habitica/res/layout/fragment_tavern_detail.xml b/Habitica/res/layout/fragment_tavern_detail.xml
deleted file mode 100644
index 372626e58a..0000000000
--- a/Habitica/res/layout/fragment_tavern_detail.xml
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Habitica/res/layout/mount_imageview.xml b/Habitica/res/layout/mount_imageview.xml
new file mode 100644
index 0000000000..9153b94096
--- /dev/null
+++ b/Habitica/res/layout/mount_imageview.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/Habitica/res/layout/pet_imageview.xml b/Habitica/res/layout/pet_imageview.xml
index 8fecaeab18..267823adb4 100644
--- a/Habitica/res/layout/pet_imageview.xml
+++ b/Habitica/res/layout/pet_imageview.xml
@@ -1,10 +1,22 @@
+ android:layout_width="match_parent"
+ android:layout_height="124dp"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_gravity="center"
+ android:background="@drawable/layout_rounded_bg_content"
+ android:clipToPadding="true"
+ android:clipChildren="true">
+
+ android:layout_gravity="center"
+ android:layout_marginTop="12dp"
+ tools:background="@color/red_10"
+ />
diff --git a/Habitica/res/layout/preference_child_summary_danger.xml b/Habitica/res/layout/preference_child_summary_danger.xml
index 7f8cba0221..8d01c05895 100644
--- a/Habitica/res/layout/preference_child_summary_danger.xml
+++ b/Habitica/res/layout/preference_child_summary_danger.xml
@@ -29,7 +29,7 @@
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/text_red"
+ android:textColor="@color/maroon100_red100"
tools:text="Title"/>
-
\ No newline at end of file
+
diff --git a/Habitica/res/layout/purchase_subscription_view.xml b/Habitica/res/layout/purchase_subscription_view.xml
index c0474f039e..b271657c6b 100644
--- a/Habitica/res/layout/purchase_subscription_view.xml
+++ b/Habitica/res/layout/purchase_subscription_view.xml
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/subscription_box_bg"
+ android:background="@drawable/subscription_type_box_bg"
android:gravity="center"
android:minHeight="106dp"
android:layout_marginBottom="8dp">
diff --git a/Habitica/res/layout/quest_collect.xml b/Habitica/res/layout/quest_collect.xml
index 30ccf4ed49..eecd2c2a5e 100644
--- a/Habitica/res/layout/quest_collect.xml
+++ b/Habitica/res/layout/quest_collect.xml
@@ -1,5 +1,4 @@
-
+ android:layout_marginEnd="16dp" />
+ app:barBackgroundColor="@color/offset_background"
+ app:barForegroundColor="@color/green_100"
+ app:valueTextColor="@color/text_ternary"/>
diff --git a/Habitica/res/layout/quest_mechanics_dialog.xml b/Habitica/res/layout/quest_mechanics_dialog.xml
new file mode 100644
index 0000000000..798ef8ca36
--- /dev/null
+++ b/Habitica/res/layout/quest_mechanics_dialog.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/layout/quest_progress_old.xml b/Habitica/res/layout/quest_progress_old.xml
index 0e64ed48a8..700d1b868d 100644
--- a/Habitica/res/layout/quest_progress_old.xml
+++ b/Habitica/res/layout/quest_progress_old.xml
@@ -6,40 +6,83 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
-
+
+
+
+
+
+
+
+
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+
+
+
+
+
+
-
-
\ No newline at end of file
+
diff --git a/Habitica/res/layout/row_shopitem.xml b/Habitica/res/layout/row_shopitem.xml
index 35e4b3aea9..63baec8c89 100644
--- a/Habitica/res/layout/row_shopitem.xml
+++ b/Habitica/res/layout/row_shopitem.xml
@@ -1,10 +1,11 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:clickable="true"
+ android:focusable="true">
-
+ android:padding="6dp">
+
+
+
diff --git a/Habitica/res/layout/shop_ad.xml b/Habitica/res/layout/shop_ad.xml
new file mode 100644
index 0000000000..cd01641944
--- /dev/null
+++ b/Habitica/res/layout/shop_ad.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
diff --git a/Habitica/res/layout/subscription_benefits.xml b/Habitica/res/layout/subscription_benefits.xml
new file mode 100644
index 0000000000..e75725d490
--- /dev/null
+++ b/Habitica/res/layout/subscription_benefits.xml
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Habitica/res/layout/task_form_task_scheduling.xml b/Habitica/res/layout/task_form_task_scheduling.xml
index 3d057574c5..c8a90deb74 100644
--- a/Habitica/res/layout/task_form_task_scheduling.xml
+++ b/Habitica/res/layout/task_form_task_scheduling.xml
@@ -103,7 +103,8 @@
tools:text="1"
android:inputType="number"
android:background="@color/transparent"
- android:textColor="@color/text_secondary"/>
+ android:textColor="@color/text_secondary"
+ android:paddingVertical="2dp"/>
-
\ No newline at end of file
+
diff --git a/Habitica/res/menu/menu_full_profile.xml b/Habitica/res/menu/menu_full_profile.xml
index 7ae69bcabe..cd2268c366 100644
--- a/Habitica/res/menu/menu_full_profile.xml
+++ b/Habitica/res/menu/menu_full_profile.xml
@@ -1,11 +1,15 @@
\ No newline at end of file
+
diff --git a/Habitica/res/menu/menu_public_guild.xml b/Habitica/res/menu/menu_public_guild.xml
deleted file mode 100644
index 9c9aa94d4c..0000000000
--- a/Habitica/res/menu/menu_public_guild.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
\ No newline at end of file
diff --git a/Habitica/res/menu/menu_share_avatar.xml b/Habitica/res/menu/menu_share_avatar.xml
new file mode 100644
index 0000000000..55e6e5aaf0
--- /dev/null
+++ b/Habitica/res/menu/menu_share_avatar.xml
@@ -0,0 +1,7 @@
+
+
diff --git a/Habitica/res/navigation/navigation.xml b/Habitica/res/navigation/navigation.xml
index c5d1e2deaa..b7e45058bb 100644
--- a/Habitica/res/navigation/navigation.xml
+++ b/Habitica/res/navigation/navigation.xml
@@ -89,24 +89,6 @@
android:label="@string/stats">
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Tareas
HabilidadesComprar Gemas
- GremiosDesafíosTiendasPersonalización del AvatarInformaciónSocialMensajes
- TabernaEquipoSuscripciónInventario
@@ -18,4 +16,4 @@
Mascotas y MonturasNoticiasAtributos
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-b+es+419/strings.tutorial.xml b/Habitica/res/values-b+es+419/strings.tutorial.xml
index 534f789702..3df99e0459 100644
--- a/Habitica/res/values-b+es+419/strings.tutorial.xml
+++ b/Habitica/res/values-b+es+419/strings.tutorial.xml
@@ -13,7 +13,6 @@
Usa las Tareas Diarias para tareas que requieran un horario regular.Presiona el botón gris para asignar los puntos de tus atributos de una sola vez, o toca las flechas para agregarlos punto por punto.¡Aquí es donde tu y tus amigos pueden apoyarse para lograr sus metas y derrotar monstruos gracias a sus tareas!
- Bienvenido a la taberna, un lugar público para todas las edades en la que podrás charlar. Por ejemplo, puedes hablar acerca de productividad y hacer preguntas. ¡Diviértete!¡Comencemos! Acabo de crear unas cuantas tareas en base a tus intereses. Intenta agregar nuevas por tu cuenta. Puedes editar cualquier misión presionando en el título.Si tu Tarea Pendiente tiene que ser hecha dentro de un cierto tiempo, configura una fecha límite. Parece que puedes marcar una — ¡Inténtalo!
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-b+es+419/strings.xml b/Habitica/res/values-b+es+419/strings.xml
index 1673f8d0ef..044bf94df2 100644
--- a/Habitica/res/values-b+es+419/strings.xml
+++ b/Habitica/res/values-b+es+419/strings.xml
@@ -176,9 +176,7 @@
Estudia las técnicas de un profesionalTrabaja en proyectos creativosTermina proyectos creativos
- Mis gremios
- Gremios públicos
- Gremio
+ GremioAbandonarUnirseBorrar
@@ -187,7 +185,6 @@
Agregar nueva etiquetaPrivacidadEscribir mensaje
- Buscar gremiosEquipamiento de combateAparienciaCabeza
@@ -658,7 +655,6 @@
Este objeto solo está disponible para una clase específica.
\nPuedes cambiar tu clase desde los Ajustes.Solo puedes comprar equipamiento para tu clase actual
- ¡Bienvenido a la Posada! Trae una silla para charlar, o toma un descanso de tus tareas.Entra a la PosadaNormas de la ComunidadVer las Normas de Seguridad
@@ -696,7 +692,6 @@
Ian el Buscador de MisionesHechicera EstacionalAtaque de Ira:
- Oh vamos, no tienes que tomarle atención a ese monstruo de abajo — Este es un lugar seguro para charlar en tus descansos.ReportarResponderPremio del Desafío
@@ -774,8 +769,6 @@
Tienes %1$s puntos de atributo sin asignarTienes un nuevo Objeto Misterioso%1$s fuiste invitado a unirte al grupo %2$s
- Fuiste invitado a ser parte del gremio privado %1$s
- Fuiste invitado a ser parte del gremio %1$sFuiste invitado a participar de la misión %1$sDerrotadoCrear
@@ -803,7 +796,7 @@
Conviértete en un %sClase %sReporta a %s por violación:
- Razón para reportar (opcional)
+ Razón para reportar (opcional)Conjuntos misteriososDescripción del GremioInvitar al Gremio
diff --git a/Habitica/res/values-be/strings.xml b/Habitica/res/values-be/strings.xml
index 6591a9e149..6b58d2af25 100755
--- a/Habitica/res/values-be/strings.xml
+++ b/Habitica/res/values-be/strings.xml
@@ -51,9 +51,7 @@
Зрабіць хатнія заданьні10 хвілінаў прыборкіПамыць посуд
- Мае гільдыі
- Публічныя гільдыі
- Гільдыя
+ ГільдыяВыйсьціДалучыццаВыдаліць
@@ -79,4 +77,4 @@
Падзяліцца
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-bg/strings.sidebar.xml b/Habitica/res/values-bg/strings.sidebar.xml
index 67dc9a364a..b7d64b07e6 100644
--- a/Habitica/res/values-bg/strings.sidebar.xml
+++ b/Habitica/res/values-bg/strings.sidebar.xml
@@ -4,9 +4,7 @@
УменияОбщностСъобщения
- КръчмаГрупа
- ГилдииПредизвикателстваИнвентарГерой
diff --git a/Habitica/res/values-bg/strings.tutorial.xml b/Habitica/res/values-bg/strings.tutorial.xml
index 610cc07645..d0c8db230e 100644
--- a/Habitica/res/values-bg/strings.tutorial.xml
+++ b/Habitica/res/values-bg/strings.tutorial.xml
@@ -14,6 +14,5 @@
Това е всичко засега. Ако искате да си припомните нещо, погледнете раздела с ЧЗВ.Уменията са специални способности, които имат мощни ефекти! Докоснете умение, за да го използвате. Това ще Ви струва мана (синята лента), която се възстановява при влизане в играта всеки ден, както и кагато изпълнявате задачите от истинския си живот. Прегледайте ЧЗВ в менюто за повече информация!Тук е мястото, където заедно с приятелите си можете взаимно да се държите отговорни за изпълнението на целите си, както и да се биете срещу чудовища със задачите си!
- Добре дошли в кръчмата — обществено място за разговори, достъпно за хора от всички възрасти! Тук можете да разговаряте за продуктивността си и да задавате въпроси. Приятно прекарване!Докоснете сивия бутон, за да разпределите по много от точките си наведнъж, или докоснете стрелките, за да добавяте по една точка.
diff --git a/Habitica/res/values-bg/strings.xml b/Habitica/res/values-bg/strings.xml
index ff0461285a..65175a4178 100644
--- a/Habitica/res/values-bg/strings.xml
+++ b/Habitica/res/values-bg/strings.xml
@@ -157,9 +157,7 @@
Да се уча от майстор в занаятаДа работя по проекта сиДа завърша проекта си
- Моите гилдии
- Обществени гилдии
- Гилдия
+ ГилдияНапусканеПрисъединяванеИзтриване
@@ -168,7 +166,6 @@
Добавяне на нов етикетПоверителностНапишете съобщение
- Търсене на гилдииБойно снаряжениеКостюмГлава
@@ -522,7 +519,6 @@
Без класТози предмет е предназначен само за определен клас.\nМожете да промените класа си от Настройките.Можете да купувате екипировка само за текущия си клас
- Добре дошли в Странноприемницата! Поседнете, за да си поговорим, или за да си починете от задачите.Влизане в странноприемницатаОбществени правилаПреглед на Обществените правила
@@ -564,7 +560,6 @@
Иън Пазителят на МисиитеСезонната магьосницаЯростна атака:
- Не обръщай внимание на чудовището по-долу – това все още е спокойно място за разговори по време на почивките Ви.ДокладванеОтговорНаграда за предизвикателството
@@ -644,7 +639,5 @@
%1$s неразпределени показателни точки]]>тайнствен предмет]]>%1$s Получихте покана за присъединяване към групата %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>
diff --git a/Habitica/res/values-ca/strings.xml b/Habitica/res/values-ca/strings.xml
index f7ac023165..a2abc1f8b2 100755
--- a/Habitica/res/values-ca/strings.xml
+++ b/Habitica/res/values-ca/strings.xml
@@ -124,9 +124,7 @@
Estudiar fins a dominar la tècnica Treballar en projecte creatiuAcabar projecte creatiu
- Els meus Gremis
- Gremis Publics
- Gremi
+ GremiAbandonarUnir-seEliminar
@@ -134,7 +132,6 @@
DescripcióPrivacitatEscriu Missatge
- Cercar GremisRoba de batallaVestitCap
diff --git a/Habitica/res/values-cs/strings.tutorial.xml b/Habitica/res/values-cs/strings.tutorial.xml
index 14acec19ec..0a7eae0a4e 100644
--- a/Habitica/res/values-cs/strings.tutorial.xml
+++ b/Habitica/res/values-cs/strings.tutorial.xml
@@ -12,5 +12,4 @@
Vytvoř si každodenní úkoly pro časově omezené úkoly, které musíš dělat pravidelně.Schopnosti jsou speciální dovednosti, které mají silné účinky! Klikni na schopnost abys ji použil. Bude tě to stát manu (modrý proužek), kterou můžeš získat tím, že se každý den připojíš, nebo plněním úkolů. Pro více informací se podívej do sekce FAQ!Zde se můžete s přáteli vzájemně motivovat k plnění svých cílů a společně porážet nestvůry vašimi úkoly!
- Vítej v Hostinci, veřejném chatu pro všechny! Zde se můžeš bavit o produktivitě a ptát se na otázky. Bav se!
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-cs/strings.xml b/Habitica/res/values-cs/strings.xml
index f60f963220..7a0cb8b233 100755
--- a/Habitica/res/values-cs/strings.xml
+++ b/Habitica/res/values-cs/strings.xml
@@ -141,9 +141,7 @@
Vypracovat předlohuPracovat na kreativním projektuDokončit kreativní projekt
- Moje cechy
- Veřejné cechy
- Cech
+ CechOdejítPřipojit seSmazat
@@ -152,7 +150,6 @@
Přidat nový ŠtítekSoukromíNapsat zprávu
- Hledat gildyZbrojKostýmHlava
diff --git a/Habitica/res/values-da/strings.xml b/Habitica/res/values-da/strings.xml
index 1c8008f53b..bbd19fc31a 100755
--- a/Habitica/res/values-da/strings.xml
+++ b/Habitica/res/values-da/strings.xml
@@ -127,16 +127,13 @@
Organiser skabArbejd på et kreativt projektFærdiggør et kreativt projekt
- Mine klaner
- Offentlige klaner
- Klan
+ KlanForladSletNavnBeskrivelsePrivatlivSkriv besked
- Søg efter klanerKampudstyrKostumeHoved
@@ -230,4 +227,4 @@
Du har modtaget en/et %1$s som belønning for din hengivenhed til at forbedre dit liv.Din næste præmie vil åbne ved %1$d Check-insafventer godkendelse
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-de/strings.sidebar.xml b/Habitica/res/values-de/strings.sidebar.xml
index cd4f938c11..35cb04ead9 100644
--- a/Habitica/res/values-de/strings.sidebar.xml
+++ b/Habitica/res/values-de/strings.sidebar.xml
@@ -4,9 +4,7 @@
FähigkeitenSozialesNachrichten
- GasthausGruppe
- GildenWettbewerbeInventarAvataranpassung
@@ -19,4 +17,4 @@
AbonnementKaufe EdelsteineAvatar & Austrüstung
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-de/strings.tutorial.xml b/Habitica/res/values-de/strings.tutorial.xml
index 22da628d6f..aee8215196 100644
--- a/Habitica/res/values-de/strings.tutorial.xml
+++ b/Habitica/res/values-de/strings.tutorial.xml
@@ -13,10 +13,9 @@
Du kannst auch benutzerdefinierte Belohnungen erstellen, die sich auf etwas in Deinem wirklichen Leben beziehen, je nachdem was Dich motiviert.Das war\'s für das Erste. Falls du eine Erinnerungshilfe brauchst, dann schau in die FAQs.Fähigkeiten erlauben es dir, mächtige Effekte auszulösen! Tippe auf eine Fähigkeit, um sie zu benutzen. Das kostet Mana (der blaue Balken), das du verdienst, indem du dich jeden Tag einloggst und reale Aufgaben abschließt. Für mehr Infos schaue im Menü in die FAQ!
- Hier können du und deine Freunde einander motivieren und mit euren Aufgaben Monster bekämpfen!
- Willkommen im Gasthaus, einem öffentlichen Chat für alle Altersklassen! Hier kannst du dich über Produktivität unterhalten und Fragen stellen. Viel Spaß!
+ Hier können du und deine Freunde einander motivieren und mit euren Aufgaben Monster bekämpfen!Tippe auf den grauen Button, um mehrere Punkte auf einmal zuzuweisen, oder tippe auf die Pfeile, um die Punkte einzeln zu verteilen.Willkommen in deiner Party! Mehr Partymitglieder findest du entweder über die Liste mit Spielern, welche nach einer Party suchen, oder indem du Freunde direkt einlädts - dafür erhältst du als Belohnung die Basi-List Questrolle.
\n
\nBesuche den Support-Bereich, um mehr über Partys zu lernen.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-de/strings.xml b/Habitica/res/values-de/strings.xml
index 208a3cbe4e..b10a313da3 100644
--- a/Habitica/res/values-de/strings.xml
+++ b/Habitica/res/values-de/strings.xml
@@ -157,9 +157,7 @@
Lerne ein HandwerkAn einem kreativem Projekt arbeitenKreatives Projekt fertigstellen
- Meine Gilden
- Öffentliche Gilden
- Gilde
+ GildeVerlassenBeitretenLöschen
@@ -168,7 +166,6 @@
Tag hinzufügenPrivatsphäreNachricht schreiben
- Nach Gilden suchenKampfausrüstungKostümKopf
@@ -522,7 +519,6 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Ohne KlasseDieser Gegenstand ist nur für eine bestimmte Klasse verfügbar.\nDu kannst Deine Klasse in den Einstellungen wechseln.Du kannst nur Ausrüstung für Deine aktuelle Klasse kaufen
- Willkommen im Gasthaus! Setze Dich dazu und chatte oder nimm Dir eine Pause von Deinen Aufgaben.Im Gasthaus erholenCommunity-RichtlinienCommunity-Richtlinien ansehen
@@ -564,7 +560,6 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Ian der QuestmeisterJahreszeitenzauberinRaserei-Angriff:
- Ihr Lieben, kümmert Euch nicht um das Monster — dies ist immer noch ein sicherer Platz für eine kleine Pause zum Chatten.MeldenAntwortenHerausforderungspreis
@@ -654,8 +649,6 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
%1$s Attributpunkt(e) verteilen]]>Überraschungsgegenstand]]>%1$s du wurdest zu der Gruppe %2$s eingeladen]]>
- Du wurdest eingeladen, der privaten Gilde %1$s beizutreten
- Du wurdest eingeladen, der Gilde %1$s beizutreten%1$s eingeladen]]>%1$s wird jeden %2$s %3$s %4$s wiederholtFertigkeiten benutzen
@@ -811,7 +804,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Gilde beitretenIn Gilde einladenGildenbeschreibung
- Grund für die Meldung (optional)
+ Grund für die Meldung (optional)Melde %s wegen eines Verstoßes:Melde einen Post **nur**, wenn er die [Community-Richtlinien](https://habitica.com/static/community-guidelines) und/oder die [Allgemeinen Geschäftsbedingungen](https://habitica.com/static/terms) verletzt. Zweckwidrige Meldungen können Dir eine Rüge einbringen.%s-Klasse
@@ -1048,9 +1041,9 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Üblicherweise %d EdelsteineEinschränkungenSo funktioniert es
- Zwischen 22. und 30. September kannst Du wie gewohnt ein Edelstein-Paket kaufen, Deinem Konto wird aber der Aktions-Betrag Edelsteine hinzugefügt. Mehr Edelsteine zum ausgeben, teilen oder aufsparen für spätere Neuerungen!
+ Zwischen %s und %s kannst Du wie gewohnt ein Edelstein-Paket kaufen, Deinem Konto wird aber der Aktions-Betrag Edelsteine hinzugefügt. Mehr Edelsteine zum ausgeben, teilen oder aufsparen für spätere Neuerungen!Der Edelstein-Sonderverkauf ist zurück! Das ist die letzte Chance, mehr Edelsteine als je zuvor zu bekommen, also füll\' Deine Lager solange Du noch kannst!
- Zwischen 29. Oktober und 2. November kannst Du wie gewohnt ein Edelstein-Paket kaufen, Deinem Konto wird aber der Aktions-Betrag Edelsteine hinzugefügt. Mehr Edelsteine zum ausgeben, teilen oder aufsparen für spätere Neuerungen!
+ Zwischen %s und %s kannst Du wie gewohnt ein Edelstein-Paket kaufen, Deinem Konto wird aber der Aktions-Betrag Edelsteine hinzugefügt. Mehr Edelsteine zum ausgeben, teilen oder aufsparen für spätere Neuerungen!Edelstein-Pakete anzeigenDie Herbstgala ist in vollem Gange. Deshalb haben wir gedacht, dass dies der perfekte Zeitpunkt für den allerersten Edelstein-Sonderverkauf ist! Jetzt bekommst Du mit jedem Einkauf mehr Edelsteine als je zuvor.Du hast %d Questgegenstände gefunden
@@ -1129,7 +1122,7 @@ Die Quest-Schriftrolle wird an den Quest-Besitzer zurückgegeben.
Offener LadenFülle diese 5 Minuten Umfrage aus, um uns beim Wachsen zu helfen und eine Errungenschaft zu erhalten!Du hast noch keine Ausrüstung für diesen Typen. Du kannst Ausrüstung vom Markt mit dem Gold, das du verdient hast, kaufen.
- ausstehend %.01f dmg
+ %s Schaden ausstehend%s verbleibendZähler zurücksetzenZähler anpassen
diff --git a/Habitica/res/values-el/strings.xml b/Habitica/res/values-el/strings.xml
index 10c1cbbb1c..e760158294 100755
--- a/Habitica/res/values-el/strings.xml
+++ b/Habitica/res/values-el/strings.xml
@@ -118,9 +118,7 @@
Διάβασμα/ΑναβολήΠλύσιμο ΠιάτωνΟργάνωση ντουλάπας
- Οι Συντεχνίες μου
- Δημόσιες Συντεχνίες
- Συντεχνία
+ ΣυντεχνίαΑποχώρησηΔιαγραφήΌνομα
@@ -128,7 +126,6 @@
Προσθήκη νέας ετικέταςΙδιωτικότηταΕγγραφή Μηνύματος
- Ψάξε για ΣυντεχνίεςΕξοπλισμός ΜάχηςΚουστούμιΚεφάλι
@@ -329,4 +326,4 @@
ΚΡΑ:ΕΥΦ:ΦΡΟ:
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-en-rGB/strings.sidebar.xml b/Habitica/res/values-en-rGB/strings.sidebar.xml
index a8d9aa77ce..34335f9fd5 100644
--- a/Habitica/res/values-en-rGB/strings.sidebar.xml
+++ b/Habitica/res/values-en-rGB/strings.sidebar.xml
@@ -3,9 +3,7 @@
TasksSkillsSocial
- TavernParty
- GuildsChallengesInventoryAvatar Customisation
@@ -18,4 +16,4 @@
StatsSubscriptionPurchase Gems
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-en-rGB/strings.tutorial.xml b/Habitica/res/values-en-rGB/strings.tutorial.xml
index 9745f2f186..5fe08e8531 100644
--- a/Habitica/res/values-en-rGB/strings.tutorial.xml
+++ b/Habitica/res/values-en-rGB/strings.tutorial.xml
@@ -14,6 +14,5 @@
That\'s all for now. If you need a reminder, check the FAQ section.Skills are special abilities that have powerful effects! Tap on a skill to use it. It will cost Mana (the blue bar), which you earn by checking in every day and by completing your real-life tasks. Check out the FAQ in the menu for more info!This is where you and your friends can hold each other accountable to your goals and fight monsters with your tasks!
- Welcome to the Tavern, a public, all-ages chatroom! Here you can chat about productivity and ask questions. Have fun!Tap the grey button to allocate lots of your stats at once, or tap the arrows to add them one point at a time.
diff --git a/Habitica/res/values-en-rGB/strings.xml b/Habitica/res/values-en-rGB/strings.xml
index f8bdac9126..2c017c1dbb 100644
--- a/Habitica/res/values-en-rGB/strings.xml
+++ b/Habitica/res/values-en-rGB/strings.xml
@@ -153,9 +153,7 @@
Study a master of the craftWork on creative projectFinish creative project
- My Guilds
- Public Guilds
- Guild
+ GuildLeaveJoinDelete
@@ -164,7 +162,6 @@
Add new TagPrivacyWrite Message
- Search for guildsBattle GearCostumeHead
@@ -446,10 +443,10 @@
Unlock by logging into Habitica regularlyUnlock by creating an accountFor %s
- 25 Gem cap
- 30 Gem cap
- 35 Gem cap
- 45 Gem cap
+ 25 Gems a month
+ 30 Gems a month
+ 35 Gems a month
+ 45 Gems a monthDueMessagesOh, you must be new here. I’m Justin, I’ll be your guide in Habitica.
@@ -528,7 +525,6 @@
This item is only available to a specific class.
\nYou can change your class from Settings.You can only purchase gear for your current class
- Welcome to the Inn! Pull up a chair to chat, or take a break from your tasks.Check into InnCommunity GuidelinesView Community Guidelines
@@ -570,7 +566,6 @@
Ian the Quest GuideSeasonal SorceressRage attack:
- Oh dear, pay no heed to the monster below — this is still a safe haven to chat on your breaks.ReportReplyChallenge Prize
@@ -654,8 +649,6 @@
%1$s unallocated Stat Points]]>Mystery Items]]>%1$s has invited you to join the party %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Gift a SubscriptionIn honor of the season of giving we\'re bringing back a very special promotion. Now when you gift somebody a subscription, you get the same sub for yourself for free!
@@ -797,7 +790,7 @@
DefeatGuild DescriptionMystery Sets
- Reason for report (optional)
+ Reason for report (optional)Report %s for violation:Invite to GuildJoin Guild
@@ -1039,7 +1032,7 @@
Block %s\?Unblock UserBlock User
- Between September 22nd and 30th, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!
+ Between September %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!LimitationsHow it works%s to %s
@@ -1050,7 +1043,7 @@
BlockA blocked user cannot send you Private Messages but you will still see their posts. This will have no effect if the person is a moderator now or in the future.The Gem Sale is back to haunt the very end of this year’s Fall Gala! This is one last chance to get more Gems than ever, so stock up while it lasts!
- Between October 29th and November 2nd, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!
+ Between %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!View Gem BundlesThe Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.
diff --git a/Habitica/res/values-es/strings.sidebar.xml b/Habitica/res/values-es/strings.sidebar.xml
index 1fd93801b6..b0cfa4f0b8 100644
--- a/Habitica/res/values-es/strings.sidebar.xml
+++ b/Habitica/res/values-es/strings.sidebar.xml
@@ -4,9 +4,7 @@
HabilidadesSocialMensajes
- TabernaEquipo
- GremiosDesafíosInventarioPersonalización del personaje
@@ -18,4 +16,4 @@
AtributosSuscripciónComprar Gemas
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-es/strings.tutorial.xml b/Habitica/res/values-es/strings.tutorial.xml
index 9a7b763c9e..98268324a7 100644
--- a/Habitica/res/values-es/strings.tutorial.xml
+++ b/Habitica/res/values-es/strings.tutorial.xml
@@ -14,6 +14,5 @@
Eso es todo por ahora. Si necesitas un recordatorio, consulta la sección de Preguntas Frecuentes.Las habilidades son competencias especiales con efectos muy poderosos. Para usar una habilidad, solo tienes que pulsar en ella. Te costará maná (la barra azul), que puedes recuperar entrando todos los días y realizando tareas en la vida real. Para obtener más información, puedes consultar la sección de preguntas frecuentes en el menú!Aquí es donde tú y tus amigos podéis estar pendientes unos de otros para cumplir vuestros objetivos y combatir monstruos con vuestras tareas!
- Te damos la bienvenida a la Taberna, una sala de chat pública para todas las edades. Aquí, puedes hablar de productividad y preguntar lo que quieras. ¡Que te diviertas!Toca el botón gris para asignar los puntos de tus atributos de una sola vez, o toca las flechas para agregarlos punto por punto.
diff --git a/Habitica/res/values-es/strings.xml b/Habitica/res/values-es/strings.xml
index 6060f627e2..99732ac7f7 100644
--- a/Habitica/res/values-es/strings.xml
+++ b/Habitica/res/values-es/strings.xml
@@ -157,9 +157,7 @@
Estudiar la técnica de un profesionalTrabajar en proyecto creativoTerminar proyecto creativo
- Mis Gremios
- Gremios públicos
- Gremio
+ GremioAbandonarUnirseBorrar
@@ -168,7 +166,6 @@
Añadir nueva etiquetaPrivacidadEscribir mensaje
- Buscar gremiosEquipo de batallaDisfrazCabeza
@@ -525,7 +522,6 @@
Sin ClaseEste objeto solo está disponible para una clase específica.\nPuedes cambiar tu clase desde Ajustes.Solo puedes comprar equipo para tu clase actual
- ¡Bienvenido a la posada! Trae una silla para conversar, o toma un descanso de sus tareas.Entrar en la PosadaNormas de la comunidadVer las Normas de la comunidad
@@ -567,7 +563,6 @@
Ian el guía de misionesHechicera de TemporadaAtaque de ira:
- Ay por favor, no prestes atención a ese monstruo. Este es aún un refugio seguro para conversar durante tus descansos.ReportarContestarPremio del Desafío
@@ -653,8 +648,6 @@
%1$s Puntos de Estadísticas sin asignar]]>Objetos Misteriosos nuevos]]>%1$s has sido invitado a unirte al Equipo %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Hace un mesUsando habilidad
@@ -840,7 +833,7 @@
Publica un mensaje en el %s para que tus preguntas sean respondidas por otros jugadores.Atributo Asignado**Únicamente** reporta una publicación que viole las [Normas de la comunidad](https://habitica.com/static/community-guidelines) y/o los [Términos de servicio](https://habitica.com/static/terms). Reportar inadecuadamente una publicación puede conllevarte una infracción.
- Motivo de la denuncia (opcional)
+ Motivo de la denuncia (opcional)Texto de la listaActividad del equipo¡Te agrupaste con un miembro del equipo!
@@ -1122,7 +1115,7 @@
Un usuario bloqueado no puede enviarte mensajes privados pero seguirás viendo sus comentarios en la Taberna o los Gremios.Suscripción única de 1 mesPulsa sobre \"Regala una Suscripción\" y escribe el usuario de la cuenta a la que quieres regalársela. Luego, elige la duración de la suscripción que quieres regalar y realizar el pago. Tu cuenta será premiada automáticamente con una suscripción igual que la que acabas de regalar.
- %.01f de daño restante
+ %s de daño restanteEclosionaHas sido invitado a unirte a un equipoFormulario para usuarios de Habitica
diff --git a/Habitica/res/values-fi/strings.xml b/Habitica/res/values-fi/strings.xml
index 69ec298a54..f4f697257b 100755
--- a/Habitica/res/values-fi/strings.xml
+++ b/Habitica/res/values-fi/strings.xml
@@ -119,9 +119,7 @@
Hio taitojasiTee jotain luovaaSain luovan projektin valmiiksi
- Minun killat
- Julkiset killat
- Kilta
+ KiltaPoistuLiityPoista
@@ -129,7 +127,6 @@
KuvausYksityisyysKirjoita viesti
- Hae kiltojaTaisteluvarustusAsuPää
@@ -242,4 +239,4 @@
tmrdbilj
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-fr/strings.sidebar.xml b/Habitica/res/values-fr/strings.sidebar.xml
index 8fa4948036..823802ee3e 100644
--- a/Habitica/res/values-fr/strings.sidebar.xml
+++ b/Habitica/res/values-fr/strings.sidebar.xml
@@ -4,9 +4,7 @@
CompétencesSocialMessages
- AubergeÉquipe
- GuildesDéfisInventairePersonnalisation de l\'avatar
@@ -19,4 +17,4 @@
AbonnementAcheter des gemmesAvatar & équipement
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-fr/strings.tutorial.xml b/Habitica/res/values-fr/strings.tutorial.xml
index 75dce74b0b..94534f655c 100644
--- a/Habitica/res/values-fr/strings.tutorial.xml
+++ b/Habitica/res/values-fr/strings.tutorial.xml
@@ -14,9 +14,6 @@
C\'est tout pour l\'instant. Si vous avez besoin d\'un rappel, consultez la section FAQ.Les compétences sont des capacités spéciales qui ont des effets puissants ! Tapez sur une compétence pour l\'utiliser. Cela vous coûtera du Mana (la barre bleue), que vous gagnez en faisant le point tous les jours et en accomplissant des tâches dans la vie réelle. Allez voir les FAQ dans le menu pour plus d\'infos !Voici l\'endroit où vos amis et vous pouvez vous tenir mutuellement responsables de vos buts et combattre des monstres grâce à vos tâches !
- Bienvenue à la Taverne, un salon de discussion public pour tous les âges ! Ici, vous pouvez discuter de productivité et poser des questions. Amusez-vous bien !Utilisez le bouton gris pour allouer plusieurs points à la fois, ou utilisez les flèches pour en ajouter un à la fois.
- Bienvenu dans votre équipe ! Vous pouvez trouver d\'autres membres dans la liste des personnes cherchant une équipe, ou en invitant directement vos amis pour recevoir le parchemin de quête du basi-liste.
-\n
-\nVisitez la section Support pour en apprendre plus sur les équipes.
-
\ No newline at end of file
+ Bienvenu dans votre équipe ! Vous pouvez trouver d\'autres membres dans la liste des personnes cherchant une équipe, ou en invitant directement vos amis pour recevoir le parchemin de quête du basi-liste.\n\nVisitez la section Support pour en apprendre plus sur les équipes.
+
diff --git a/Habitica/res/values-fr/strings.xml b/Habitica/res/values-fr/strings.xml
index 7022e2355a..44abb1f445 100644
--- a/Habitica/res/values-fr/strings.xml
+++ b/Habitica/res/values-fr/strings.xml
@@ -156,9 +156,7 @@
Inspirez-vous du travail d\'un mentorTravailler sur un projet créatifFinir un projet créatif
- Mes guildes
- Guildes publiques
- Guilde
+ GuildeQuitterRejoindreSupprimer
@@ -167,7 +165,6 @@
Ajouter une nouvelle étiquetteSécuritéÉcrire un message
- Chercher des guildesÉquipement de combatCostumeTête
@@ -524,7 +521,6 @@
Cet objet est disponible pour une classe spécifique.
\nVous pouvez changer de classe depuis les paramètres.Vous ne pouvez acheter de l\'équipement que pour votre classe actuelle
- Bienvenue à la taverne ! Prenez une chaise pour vous joindre à la conversation.Se reposer à l\'aubergeRègles de vie en communautéVoir les règles de vie en communauté
@@ -566,7 +562,6 @@
Ian le guide des quêtesSorcière saisonnièreAttaque de rage :
- Oh, ne faites pas attention au monstre ci-dessous — c\'est toujours un endroit sûr pour discuter pendant vos pauses.SignalerRépondreRécompense du défi
@@ -652,8 +647,6 @@
%1$s points d\'attribut non alloués]]>objets mystère]]>%1$s vous avez reçu une invitation à rejoindre l\'équipe %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>1 heure restante%d heures restantes
@@ -748,7 +741,7 @@
Inviter dans la guildeDescription de la guildeEnsembles mystère
- Motif du signalement (optionnel)
+ Motif du signalement (optionnel)Signaler %s pour violation :Ne signalez **que** les messages qui enfreignent les [règles de vie en communauté](https://habitica.com/static/community-guidelines) et/ou les [conditions de service](https://habitica.com/static/terms). Signaler des messages de façon inadéquate pourrait vous valoir une infraction.Classe %s
@@ -1127,7 +1120,7 @@
Conditions d\'utilisationPolitique de ConfidentialitéVous avez fini toutes vos tâches. Bravo !
- %.01f dégâts en cours
+ %s dégâts en coursRéinitialiser le compteurAjuster le compteur%s restant
@@ -1278,4 +1271,4 @@
%d ConnexionsEnvoyer une invitationVos +%d crédits de mois d\'abonnement s\'activeront après avoir annulé
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-hr-rHR/strings.sidebar.xml b/Habitica/res/values-hr-rHR/strings.sidebar.xml
index e8e9c6d055..fc531caf09 100644
--- a/Habitica/res/values-hr-rHR/strings.sidebar.xml
+++ b/Habitica/res/values-hr-rHR/strings.sidebar.xml
@@ -4,9 +4,7 @@
VještineDruštvoPoruke
- KrčmaDružina
- CehoviIzazoviInventarUređivanje Avatara
@@ -19,4 +17,4 @@
Kupi draguljePretplataAvatar i oprema
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-hr-rHR/strings.tutorial.xml b/Habitica/res/values-hr-rHR/strings.tutorial.xml
index cd07f2f1d9..f2c357b8d1 100644
--- a/Habitica/res/values-hr-rHR/strings.tutorial.xml
+++ b/Habitica/res/values-hr-rHR/strings.tutorial.xml
@@ -14,9 +14,6 @@
To je to za sada. Ako ti treba podsjetnik, pogledaj odjeljak Često postavljenih pitanja.Vještine su posebne sposobnosti koje imaju jake učinke! Klikni na sposobnost kako bi ju iskoristio/la. Koštat će te mane (plava traka), koju zaradiš prijavljujući se svaki dan i izvršavajući svoje zadatke u stvarnom životu. Provjeri Često Postavljena Pitanja u meniju za više informacija!Ovo je mjesto gdje se ti i tvoji prijatelji možete držati svojih ciljeva i boriti protiv čudovišta pomoću svojih zadataka!
- Dobrodošao/la u Krčmu, javni chat za ljude svih dobi! Ovdje možeš razgovarati o produktivnosti i postavljati pitanja. Zabavi se!Klikni na sivi gumb kako bi raspodijelio/la mnoštvo svojih statističkih bodova odjednom, ili klikni na strelice kako bi ih dodavao/la jednog po jednog.
- Dobrodošao/la u svoju Družinu! Možeš naći nove članove na popisu igrača koji traže Družinu ili pozvati prijatelje kako bi dobio/la Svitak za Bazi-Lista Pustolovinu.
-\n
-\nPosjeti Podršku kako bi naučio/la više o Družinama.
-
\ No newline at end of file
+ Dobrodošao/la u svoju Družinu! Možeš naći nove članove na popisu igrača koji traže Družinu ili pozvati prijatelje kako bi dobio/la Svitak za Bazi-Lista Pustolovinu.\n\nPosjeti Podršku kako bi naučio/la više o Družinama.
+
diff --git a/Habitica/res/values-hr-rHR/strings.xml b/Habitica/res/values-hr-rHR/strings.xml
index 657ab2276f..b50b722fd7 100644
--- a/Habitica/res/values-hr-rHR/strings.xml
+++ b/Habitica/res/values-hr-rHR/strings.xml
@@ -157,9 +157,7 @@
Uči od majstora vještineRadi na kreativnom projektu Završi kreativni projekt
- Moji Cehovi
- Javni Cehovi
- Ceh
+ CehNapustiPridruži seIzbriši
@@ -168,7 +166,6 @@
Dodaj novu oznakuPrivatnost Napiši poruku
- Pretraži cehoveBorbena OpremaKostimGlava
@@ -513,7 +510,6 @@
Bez klaseOvaj artikal je dostupan samo određenoj klasi.\nMožeš promijeniti svoju klasu u Postavkama.Možeš kupovati opremu samo za svoju trenutnu klasu
- Dobrodošao/la u Gostionicu! Dovuci stolicu i priključi se razgovoru.Prijavi se u SvratišteSmjernice za zajednicuPogledaj Smjernice za zajednicu
@@ -555,7 +551,6 @@
Ian, Vodič kroz PustolovineSezonska ČarobnicaNapad Bijesa:
- Ajoj, ne obračaj pozornost na čudovište ispod - ovo je i dalje sigurno utočište za čavrljanje u pauzama.PrijaviOdgovoriNagrada za Izazov
@@ -699,7 +694,6 @@
PorazDostupno do %sPrijavi se uz Google
- Pozvan/a si da se pridružiš Cehu %1$sPozvan/a si u Pustolovinu %1$sNapusti i izbriši zadatkeObavijesti
@@ -796,7 +790,7 @@
Tema aplikacijePrihvatiKoristi obavijesti e-pošte
- Razlog prijave (neobavezno)
+ Razlog prijave (neobavezno)Mistični setoviNapusti CehOpis Ceha
@@ -1088,7 +1082,7 @@
Ovo je vremenski ograničeno događanje koje počinje %s (13:00 UTC) i završava %s (01:00 UTC). Ova promocija se samo primjenjuje kada poklanjaš drugom Habiticanu. Ako ti ili tvoj primatelj poklona već imate pretplatu, poklonjena pretplata će dodati mjesece kredita koji će biti iskorišteni samo nakon što je trenutna pretplata otkazana ili kad istekne.Još nemaš ovakvu opremu. Možeš kupiti opremu iz dućana koristeći zlato koje si zaradio/la.Nisi član nikakvih Izazova.
- %.01f štete čeka
+ %s štete čekaJednokratna pretplata od 1 mjesecNisi član nikakvih Cehova.Rasprodaja završava za %s
@@ -1421,4 +1415,4 @@
Ti, %d drugeTi, %d drugih
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-hu/strings.xml b/Habitica/res/values-hu/strings.xml
index a958a83c76..b7dd5e25fa 100755
--- a/Habitica/res/values-hu/strings.xml
+++ b/Habitica/res/values-hu/strings.xml
@@ -231,4 +231,4 @@
RésztvevőkMeghívókKert
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-in/strings.sidebar.xml b/Habitica/res/values-in/strings.sidebar.xml
index d5e21a883f..2a4a12deb7 100644
--- a/Habitica/res/values-in/strings.sidebar.xml
+++ b/Habitica/res/values-in/strings.sidebar.xml
@@ -4,9 +4,7 @@
KemampuanSosialPesan
- Kedai MinumanParty
- GuildTantanganInventoriAvatar
@@ -17,6 +15,5 @@
TokoStatusBerlangganan
- Beli Permata
- Avatar & Perlengkapan
-
\ No newline at end of file
+ Membeli Permata
+
diff --git a/Habitica/res/values-in/strings.tutorial.xml b/Habitica/res/values-in/strings.tutorial.xml
index 13e1d58ed8..22ba649ea7 100644
--- a/Habitica/res/values-in/strings.tutorial.xml
+++ b/Habitica/res/values-in/strings.tutorial.xml
@@ -1,22 +1,18 @@
-
+
- Di sinilah kita! Aku sudah membuatkan beberapa tugas berdasarkan minatmu. Coba kamu tambahkan beberapa tugas lagi. Kamu bisa mengedit tugas dengan mengetuk judulnya.
- Pertama adalah Kebiasaan. Bisa Kebiasaan positif yang ingin kamu tingkatkan atau Kebiasaan negatif yang ingin kamu tinggalkan.
- Setiap kali kamu melakukan Kebiasaan positif, ketuk tanda + untuk memperoleh pengalaman dan koin emas!
- Kalau kamu lalai dan melakukan sebuah Kebiasaan negatif, mengetuk tanda - akan membuat nyawa avatarmu berkurang supaya kamu belajar bertanggung jawab.
- Coba deh! Kamu bisa mengeksplor macam-macam tugas lain melalui tombol navigasi di bawah.
- Buat Keseharian untuk tugas-tugas yang terikat waktu dan butuh untuk diselesaikan secara berkala.
- Hati-hati — kalau ada yang terlewat, avatar kamu akan terkena damage besoknya. Menyelesaikan Keseharian secara konsisten memberi banyak keuntungan!
- Gunakan To Do untuk mencatat tugas-tugas yang hanya perlu kamu lakukan sekali.
- Kalau To Do-mu harus diselesaikan pada waktu tertentu, tetapkan tenggat waktunya. Sepertinya ada satu yang bisa kamu centang sekarang — sana!
- Beli perlengkapan untuk avatarmu menggunakan koin emas yang kamu dapatkan!
- Kamu juga bisa membuat Hadiah yang benar-benar ada di dunia nyata tergantung apa yang memotivasimu.
- Itu dulu untuk sekarang. Kalau kamu butuh pengingat, lihat bagian FAQ.
+ Di sinilah kita! Aku sudah membuatkan beberapa tugas berdasarkan minatmu. Coba kamu tambahkan beberapa tugas lagi. Kamu bisa mengedit tugas dengan mengetuk judulnya.
+ Pertama adalah Kebiasaan. Bisa Kebiasaan positif yang ingin kamu tingkatkan atau Kebiasaan negatif yang ingin kamu tinggalkan.
+ Setiap kali kamu melakukan Kebiasaan positif, ketuk tanda + untuk memperoleh pengalaman dan koin emas!
+ Kalau kamu lalai dan melakukan sebuah Kebiasaan negatif, mengetuk tanda - akan membuat nyawa avatarmu berkurang supaya kamu belajar bertanggung jawab.
+ Coba deh! Kamu bisa mengeksplor macam-macam tugas lain melalui tombol navigasi di bawah.
+ Buat Keseharian untuk tugas-tugas yang terikat waktu dan butuh untuk diselesaikan secara berkala.
+ Hati-hati — kalau ada yang terlewat, avatar kamu akan terkena damage besoknya. Menyelesaikan Keseharian secara konsisten memberi banyak keuntungan!
+ Gunakan To Do untuk mencatat tugas-tugas yang hanya perlu kamu lakukan sekali.
+ Kalau To Do-mu harus diselesaikan pada waktu tertentu, tetapkan tenggat waktunya. Sepertinya ada satu yang bisa kamu centang sekarang — sana!
+ Beli perlengkapan untuk avatarmu menggunakan koin emas yang kamu dapatkan!
+ Kamu juga bisa membuat Hadiah yang benar-benar ada di dunia nyata tergantung apa yang memotivasimu.
+ Itu dulu untuk sekarang. Kalau kamu butuh pengingat, lihat bagian FAQ.Kemampuan adalah hal spesial yang memiliki efek yang kuat! Ketuk pada kemampuan untuk menggunakannya. Kemampuan akan mengurangi Mana (bar biru), yang bisa kamu dapatkan dengan mengecek aplikasi setiap hari dan dengan menyelesaikan tugas dunia-nyatamu. Periksa FAQ pada menu untuk info lebih lanjut!
- Ini adalah bagian dimana kamu dan teman-temanmu dapat menjaga masing-masing agar tetap bertanggung jawab sekaligus melawan monster-monster dengan tugasmu!
- Selamat datang di Kedai Minuman, ruang berbincang publik untuk semua umur! Di sini kamu dapat berbincang mengenai produktivitas dan menanyakan pertanyaan. Selamat bersenang-senang!
+ Ini adalah bagian dimana kamu dan teman-temanmu dapat menjaga masing-masing agar tetap bertanggung jawab sekaligus melawan monster-monster dengan tugasmu!Ketuk tombol abu-abu untuk mengalokasikan banyak status sekaligus, atau ketuk pada tanda panah untuk menambahkan satu per satu.
- Selamat datang di Party-mu! Kamu bisa menemukan lebih banyak anggota dari daftar pemain yang mencari Party atau undang teman secara langsung untuk mendapatkan Gulungan Misi Basi-List.
-\n
-\nKunjungi Dukungan untuk mempelajari lebih lanjut tentang Party.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-in/strings.xml b/Habitica/res/values-in/strings.xml
index 92beea320c..2e6460fc10 100644
--- a/Habitica/res/values-in/strings.xml
+++ b/Habitica/res/values-in/strings.xml
@@ -67,7 +67,7 @@
MingguHore!Jangan bersedih!
- Nyawamu habis!
+ Kamu kehabisan Nyawa!Isi ulang Nyawa & Ulang LagiFilterKamu menggunakan %1$s.
@@ -109,7 +109,7 @@
Batalkan UndanganBatalkan MisiVersi %1$s (%2$d)
- Dukungan
+ Bantuan & FAQAku mengerti!Ingatkan aku lagiSelamat datang di
@@ -153,9 +153,7 @@
Belajar menguasai kerajinan tanganMengerjakan proyek kreatifMenyelesaikan proyek kreatif
- Guild-ku
- Guild Publik
- Guild
+ GuildTinggalkanGabungHapus
@@ -164,7 +162,6 @@
Tambahkan Label baruPrivasiTulis Pesan
- Cari guildPerlengkapan BertarungKostumKepala
@@ -208,7 +205,7 @@
\n • Runtunan tugas dan hitungan Habit tidak akan hilang
\n • Damage-mu kepada Boss atau item misi yang terkumpul statusnya akan tetap tertunda hingga kamu keluar dari PenginapanKamu tidak memiliki %s
- Lvl. %1$d %2$s
+ Level %1$d %2$sPrajuritPerampokPenyihir
@@ -220,6 +217,7 @@
Matikan FiturApakah kamu yakin ingin menjadi seorang %s?Kamu sekarang seorang %s!
+
Pilih KelasKembaliApakah kamu yakin kamu ingin mematikan fitur?
@@ -230,7 +228,7 @@
Melalui EmailUndang Pengguna yang telah adaKirim
- Temukan Anggota
+ Undang TemanJika seorang teman bergabung dengan Habitica melalui emailmu, mereka otomatis akan diundang ke dalam kelompokmu!Tambahkan UndanganEmail
@@ -239,8 +237,8 @@
Saya menetaskan peliharaan seekor %2$s %1$s di #Habitica dengan menyelesaikan tugas kehidupan-nyata saya!Saya baru mendapatkan tunggangan seekor %1$s di Habitica dengan menyelesaikan tugas kehidupan-nyata saya!Buka di Play Store
- Apakah kamu yakin ingin mengubah profesimu seharga 3 Permata\?
- Ini akan mengembalikan poin statmu, mengganti perlengkapan yang terbuka di toko, dan mengubah Kemampuanmu.
+ Apa kamu yakin untuk mengubah pekerjaanmu\? Ini akan membutuhkan 3 permata.
+ Peringatan: Kamu tidak akan dapat membeli perlengkapan untuk profesi %s. YakinPasarPenjelajah Waktu
@@ -251,7 +249,7 @@
Buka dengan membuat sebuah akun%1$s telah dibeliPesan telah tersalin di Papan pencatat
- Ubah
+ RubahKamu yakin?Apa kamu yakin ingin menghapus?
@@ -278,7 +276,7 @@
Kemampuan untuk mengganti profesi sebelum level 100Dukung Habiticagunakan
- Tidak mendapatkan item yang diinginkan\? Beli di Pasar!
+ Tidak mendapatkan item yang diinginkan? Beli di Market!Butuh makanan yang berbeda? Beli lebih dari pasar!Buka PasarKeseharianmu akan reset pada saat kamu membuka Habitica di atas %1$s. Pastikan kamu telah menyelesaikan Keseharianmu sebelum jam tersebut!
@@ -289,7 +287,7 @@
Milik TantanganMemiliki PengingatMemiliki Label
- Berlangganan mendukung para pengembang dan membantu menjaga Habitica terus berjalan, terima kasih!
+ Berlangganan mendukung para pengembang dan membantu menjaga Habitica terus berjalanJadilah seorang pelanggan untuk mendapatkan keuntungan eksklusif!Beli permata dengan koin emasItem bulanan eksklusif
@@ -298,7 +296,7 @@
LanggananBerulang setiap %sBerlangganan
- Kamu dapat membeli Permata dari Pasar dengan harga satuan 20 Koin Emas!
+ Kamu dapat membeli Permata dari Market dengan harga satuan 20 Koin Emas!Dapatkan Jam Pasir Mistik untuk membeli item di Toko Penjelajah Waktu!Langganan sekarang untuk memperoleh perlengkapan eksklusif sekarang juga dan menerima item-item baru setiap bulan!Terima peliharaan Jackalope Ungu Kerajaan ketika kamu menjadi pelanggan baru.
@@ -491,7 +489,6 @@
Tanpa PekerjaanItem ini hanya tersedia untuk pekerjaan tetentu. \nKamu bisa menggantu pekerjaanmu dari Pengaturan.Kamu hanya bisa membeli perlengkapan untuk pekerjaanmu yang sekarang
- Selamat datang di Penginapan! Tarik kursi dan mari mengobrol.Masuk ke PenginapanPedoman KomunitasLihat Pedoman Komunitas
@@ -611,7 +608,7 @@
membuat akunCheckin yang ke-%dMasuk ke Habitica secara teratur
- Temukan Anggota
+ Mengundang TemanTerbuka setelah mengecek Habitica %d kali.Prajurit memberikan pukulan kritikal lebih banyak dan daya rusak yang besar terhadap Boss. Bermainlah sebagai Prajurit jika kamu ingin mengalahkan monster dengan mudah! Prajurit memiliki nilai plus dari stat Kekuatan yang tinggi.Kamu akan kehilangan seluruh level, Koin Emas, dan poin pengalamanmu. Semua tugasmu dan data riwayatnya akan terhapus (tugas dari Tantangan tetap tersimpan). Kamu akan kehilangan seluruh perlengkapanmu, termasuk perlengkapan edisi terbatas dan perlengkapan berlangganan, tapi kamu masih dapat membelinya kembali (kamu perlu berada di pekerjaan yang sesuai untuk membeli perlengkapan spesifik untuk suatu perlengkapan). Kamu akan tetap memiliki pekerjaanmu saat ini, peliharaan, serta tungganganmu. Untuk konfirmasi, ketik RESET di kolom di bawah ini.
@@ -641,7 +638,6 @@
Membuat TantanganKategori TantanganHadiah Tantangan
- Oh sayangku, tak perlu kau hiraukan monster dibawah — ini adalah tempat yang aman untuk ngobrol selama kamu beristirahat.Serangan Amarah:Penyihir MusimanPeriksa Kedai Minum untuk melihat progres Boss dan serangan Amarah
@@ -680,13 +676,13 @@
Nama Masuk adalah nama pengguna unik saat ini yang dapat terlihat disamping layar namamu dan digunakan untuk undangan, mengobrol dengan @mentions, dan bertukar pesan.Ini waktunya untuk membuat nama penggunamu!Kedua Tangan
- Total Cek-In
+ Total Check-InCheck-In TerakhirTerkadang memulai hal yang baru adalah pilihan yang terbaik, Habitica siap membantu!Butuh rutinitas yang berbeda\?Centang tugasmu di Habitica untuk mendapat hadiah dan XP!Sudah selesai semua\?
- Ulang Rentetan
+ Mengulang BeruntutanTunjukkan Lebih SedikitBaca Lebih LanjutStaf
@@ -706,8 +702,8 @@
Konfirmasi penghapusanApakah kamu ingin tetap berpartisipasi dalam Tantangan saat meninggalkan Perkumpulan\?Apakah kamu yakin ingin keluar dari perkumpulan ini\?
- Kamu mengirimkan %1$s berlangganan %2$s-bulan Habitica.
- Kamu mengirimkan %1$s sebuah berlangganan %2$s-bulan dan berlangganan yang sama telah diterapkan pada akunmu melalui promo kami Beri Satu Dapat Satu!
+ Kamu mengirimkan %s berlangganan %s-bulan Habitica.
+ Kamu mengirimkan %s sebuah berlangganan %s-bulan dan berlangganan yang sama telah diterapkan pada akunmu melalui promo kami Beri Satu Dapat Satu!Update tersedia: %1$s (%2$d)BeruntutanPengingat baru
@@ -730,8 +726,6 @@
Hanya ketua yang dapat membuat TantanganKalahKamu telah diundang untuk melaksanakan Misi %1$s
- Kamu telah diundang untuk bergabung dengan Guild %1$s
- Kamu telah diundang untuk bergabung dengan Guild pribadi %1$s%1$s Kamu telah diundang untuk bergabung dengan Party %2$sKamu memiliki Item Misteri baruKamu memiliki %1$s Poin Stat yang belum dialokasikan
@@ -766,7 +760,7 @@
tLaporkan pelanggaranSet Misteri
- Alasan pelaporan (opsional)
+ Alasan pelaporan (opsional)Melaporkan %s atas pelanggaran:**Laporkan** hanya jika postingan tersebut melanggar [Pedoman Komunitas](https://habitica.com/static/community-guidelines) dan/atau [Syarat Layanan](https://habitica.com/static/terms). Laporan yang tidak sesuai dapat memberimu pelanggaran.Menjadi %s
@@ -845,19 +839,19 @@
Sistem Pekerjaan Terbuka!Pilihlah kelas yang sesuai dengan gaya permainanmu dan buka kemampuan khusus dan baju pelindung untuk membantumu di perjalananTidak sekarang
- Hapus Pengingat
- Hapus Tugas
- Undang ke Party
+ Menghapus Pengingat
+ Menghapus Tugas
+ Mengundang ke PartyBank GuildSaya setuju untuk mengikuti pedomanTamanBergabung dengan GuildMengundang ke GuildDeskripsi Guild
- Ketuk \"Hadiahkan fitur Langganan\" dan ketik nama pengguna akun yang ingin kamu berikan. Kemudian, pilih jangka masa berlangganan yang ingin kamu berikan dan bayar. Akunmu secara otomatis akan diberikan hadiah berlangganan yang serupa.
+ Sentuh \"Memberikan Berlangganan\" dan ketik akun nama pengguna yang kamu berikan. Kemudian, pilih jangka waktu berlangganan yang ingin kamu berikan dan bayar. Akunmu secara otomatis akan diberikan hadiah berlangganan yang sama.Bagaimana cara kerjanya
- Hadiahkan fitur Langganan
- Untuk menghormati musim pemberian kami menghadirkan kembali sebuah promosi yang sangat spesial. Sekarang kapanpun kamu memberikan seseorang berlangganan, kamu akan dapat berlangganan yang serupa secara cuma-cuma!
+ Berikan Berlangganan
+ Sebagai penghormatan musim pemberian ini kami menghadirkan kembali sebuah promosi yang sangat spesial. Sekarang ketika kamu memberikan seseorang sebuah berlangganan, kamu akan mendapatkan berlangganan yang sama secara cuma-cuma!DiskonEvent Beri Satu, Dapat SatuBerikan berlangganan sekarang dan dapatkan berlangganan yang sama gratis!
@@ -879,7 +873,7 @@
Tidak BerulangMembatalkanMasuk dengan Apple
- Kirim Undangan
+ Mengirim UndanganGoogleFacebooklokal
@@ -958,457 +952,4 @@
Kamu mengobrak-abrik isi Peti dan menemukan makanan. Kenapa bisa ada di sini\?JualBuat party baru
- Akademik
- Habitica Resmi
- Kerohanian
-
- setiap %d bulan
-
- Sinkronisasi Manual atau Mulai Ulang
- Terkadang aplikasi tidak memperbarui konten secara otomatis. Coba tarik untuk menyegarkan atau menutup paksa aplikasi dan membukanya kembali.
- Perbarui Aplikasi
- Kami terus mendorong perbaikan baru, jadi pastikan untuk memeriksa Play Store untuk melihat apakah ada pembaruan yang tersedia.
- Dukungan
- Pertanyaan Habitica
- Tidak dapat menemukan pengguna
- Ikat kepala
- Kamu tidak dapat membeli sebanyak itu.
- Setiap Peliharaan punya satu jenis makanan yang mereka sukai! Bereksperimenlah untuk mencari tahu mana yang tercepat menumbuhkan Peliharaanmu
- Selesaikan tujuan-tujuan ini dan kamu akan mendapatkan <b>5 Pencapaian</b> dan <font color=#EE9109><b>100 Emas</b></font> setelah selesai!
- Lanjutkan! Jika kamu perlu bantuan untuk merencanakan tugas, coba pikirkan hal apa yang ingin kamu lakukan pada waktu tertentu dalam sehari
- cth: habitrabbit atau gryphon@example.com
- Pelanggan mendapatkan Jam Pasir Mistik selama tiga hari pertama pada tiap bulan.
- Ayo mulai
- Beri makan peliharaan
- Peliharaan menetas
- Peliharaan diberi makan
- Ada banyak sekali Peliharaan untuk dikoleksi, kamu pasti punya satu yang favorit. Jika kamu beri makan, mereka bisa saja bertumbuh…
- Menyelesaikan tugas memberimu kesempatan untuk memperoleh telur, ramuan penetas, dan makanan peliharaan.
- Pergi ke Item milikmu dan cobalah mengombinasikan telur barumu dengan Ramuan Penetas!
- Hapus Tugas Tantangan\?
- Ramuan Ajaib
- Ramuan Ajaib
- Gunakan Sadel
- Tinggalkan & Hapus Tugas
- Blokir
- Pesan Pribadi dinonaktifkan
- Kamu masih dapat mengirimkan pesan, namun orang lain tidak mengirimkan pesan padamu. Kamu bisa mengaktifkan ini kembali dari Pengaturan.
- %1$s ke %2$s
- Bagaimana cara kerjanya
- Gala Musim Gugur sedang berlangsung maka kami pikir ini adalah waktu yang tepat untuk Diskon Permata! Sekarang kamu dapat memperoleh lebih banyak Permata daripada sebelumnya dalam setiap pembelian.
- Diskon Permata kembali menghantui hingga akhir Gala Musim Gugur tahun ini! Ini adalah kesempatan terakhir untuk mendapat Permata lebih banyak dari biasanya, jadi timbun sebanyak-banyaknya selagi bisa!
- Selesai
- Kamu
- Kamu diundang bergabung dengan Party
- Menetralkan kembali semua tugas (warna kuning), dan mengembalikan semua Nyawa yang hilang
- Ini akan langsung berefek setelah pembelian!
- Buat Guild
- Buka Situs
- Akhiri Tantangan
- Untuk mengakhiri Tantangan, masuk ke situs Habitica kemudian ketuk tombol \"Akhiri Tantangan\" pada layar Tantangan di sebelah kanan.
- Kamu memenangkan Tantangan
- Selamat!
- %s memilihmu sebagai pemenang! Kemenanganmu telah tersimpan di Pencapaian.
- 12 bulan langganan tidak berulang
- Tidak bisa menyukai pesanmu sendiri
- Apakah kamu yakin kamu mau meninggalkan misi yang sedang berlangsung\? Semua perkembangan misimu akan hilang.
- Apakah kamu yakin kamu mau meninggalkan Misi ini\? Kamu tidak akan bisa berpartisipasi lagi.
- Layar Utama
- Baru
- Buka sampai %s
- Kotak centang harian
- Tidak ada apa-apa di sini
- Kamu bukan anggota Guild mana pun.
- Tambahkan sandi
- Hubungkan
- Putuskan
- Tambah
- Sandi disimpan
- Salin Token. Hati-hati, ini kata sandi!
- Menambahkan autentikasi %s
- %s disalin ke clipboard
- %s tidak terhubung
- Tambahkan Email dan Kata Sandi
- Alamat Email tidak valid
- Pembelian berhasil
- Peralatan Baru ditambah setiap bulan. Jika kamu sudah memiliki semua peralatan, maka kamu akan mendapatkan Makanan atau Pengalaman, dengan peluang 50/50.
- Tapi kamu bisa mendapatkan semuanya kembali dengan kerja keras! Semoga berhasil—kamu pasti melakukannya dengan baik.
- Peralatan Tersisa: %d
- Peralatan baru ditambahkan setiap bulan
- Tonton iklan
- Tersedia dalam %s
- Tonton iklan untuk buka lagi
- Tonton Iklan untuk hidup kembali
- Peti
- Tingkat perolehan dari Peti Ajaib
- 60% Peralatan
- 20% Poin pengalaman
- Jumlah yang diperoleh bervariasi mulai dari 10 sampai 50
- Pengaturan Permulaan Hari
- Dibeli
- Perlihatkan
- Filter Latar Belakang
- Terlama
- Urutkan Berdasarkan
- Januari
- Februari
- Maret
- Mei
- Juni
- Juli
- Agustus
- Desember
- Sesuaikan kapan harimu berganti setelah tengah malam.
- Beli Set ini
- Tutorial-mu diulang
- Nyawamu habis!
- Peralatan yang rusak dapat dibeli kembali dari Hadiah
- Kamu akan jatuh ke level %d, kehilangan %d Koin Emas, dan merusak sebuah perlengkapan…
- Penyesuaian Kumis Avatar
- Advokasi + Pergerakan
- Tampilan daftar Tugas
- Perlihatkan tugas yang ditetapkan dan buka di daftar tugas pribadimu
- Salin tugas bersama
- Pengaturan Paket Grup
- Lv. %d
- Apakah kamu mau mengganti profesimu ke %1$s dengan 3 Permata\?
- rb
- jt
- mil
- Kamu yakin mau reset\?
- Checkin yang ke-%d
- Kosongkan Cache
- Untuk menghapus cache, buka aplikasi Setelan di ponsel. Buka Penyimpanan > App > Habitica, lalu ketuk Hapus Cache.
- Ekor Hewan
- Aksesoris
- Beli Semua
- Selesaikan untuk memperoleh <font color=#EE9109><b>100 Koin Emas</b></font>!
- Hapus…
- Mulai
- %d%% Terselesaikan
- Tetaskan peliharaan
- Perlengkapan adalah sebuah cara untuk mengkustomisasi avatarmu dan menambah stat
- Tugas pertamamu dibuat
- Tugas terselesaikan
- Perlengkapan dapat berguna atau hanya sebagai penampilan semata. Tingkatkan stat milikmu untuk mendapatkan berbagai macam keuntungan pada avatarmu
- Jika kamu menginginkan lebih, lihat bagian Pencapaian dan mulailah mengumpulkannya!
- Kamu menyelesaikan Tugas Pengenalan!
- Item Berlebih
- Beli %d
- Pakai
- Lepaskan
- Kamu masih membutuhkan telur %s untuk menetaskan hewan peliharaan ini
- Kamu membutuhkan %1$s dan ramuan %2$s untuk menetaskan hewan peliharaan ini
- Kamu masih membutuhkan telur %s untuk menetaskan hewan peliharaan ini lagi
- Kombinasikan Telur %1$s dan Ramuan %2$s untuk menetaskan peliharaan ini!
- Peliharaan Belum Ditetaskan
- Tetaskan Peliharaan lagi
- Tetaskan
- Tinggalkan & Hapus %d Tugas
- Tantangan yang Selesai
- Simpan %d Tugas
- Tugas Tantangan %s
- Peliharaan %s
- Tunggangan %s
- Blokir Pengguna
- Buka Blokir Pengguna
- Blokir %s\?
- Pengguna yang terblokir tidak dapat mengirimkan Pesan Pribadi kepadamu tapi kamu masih tetap bisa melihat postingan mereka.
- Antara %s dan %s, cukup beli paket Permata mana saja dan akumu akan dikirimkan Permata dengan jumlah tersebut. Semakin banyak Permata untuk digunakan, dibagikan, atau disimpan untuk kebutuhan mendatang!
- Promosi ini hanya berlaku selama event terbatas berlangsung. Event ini dimulai pada %s (12:00 UTC) dan berakhir pada %s (00:00 UTC). Penawaran promo ini hanya tersedia untuk pembelian Permata untuk dirimu sendiri.
- Pengguna yang terblokir tidak dapat mengirimkan Pesan Pribadi kepadamu tapi kamu masih tetap bisa melihat postingan mereka.
- Event ini berlangsung terbatas dimulai dari %s (13:00 UTC) dan berakhir pada %s (01:00 UTC). Masa Promosi ini hanya berlaku ketika kamu memberikannya kepada Habitican yang lain. Jika kamu atau penerima telah mempunyai berlangganan sebelumnya, maka hadiah berlangganan akan ditambahkan pada masa berlangganan yang sedang berlaku dan dimulai setelah masa berlangganan saat ini habis.
- Tim
- Informasi Tim
- Kamu membuka Kotak Misteri dan menemukan…
- Item musiman tersedia
- Tidak lagi tersedia
- Pergi ke Survei
- Survei pemain Habitica
- Kamu menyebabkan kerusakan pada bos
- Gaya
- Buka Toko
- Kamu bukan anggota Tantangan apa pun.
- Kamu menyelesaikan semua tugasmu. Bagus!
- Kebijakan Privasi
- Profil Publik
- Tentangku
- Info Akun
- Cara Masuk
- Kata sandi harus diketik dengan benar dua kali
- Minimal
- Apakah kamu yakin mau menghapusnya\?
- 20% Makanan
- April
- September
- Oktober
- November
- Reset Akun
- Kreativitas
- Hiburan
- Keuangan
- Kesehatan + Kebugaran
- Tidak Diatur
- Berdasarkan Lokasi
- Kesehatan Mental + Perawatan Diri
- Pengembangan Diri
- Ringkasan Tugas
- Ini adalah **%1$s** Kebiasaan yang **%2$s**.
- Daftar Anggota
- Amazon
- Apple Pay
- pada setiap hari dalam seminggu
- pada %1$s %2$s bulan ini
- kedua
- ketiga
- keempat
- Peliharaan Gryphatrice Gembira
- Edisi Terbatas
- Terima kasih atas dukunganmu!
- Kamu membeli Gryphatrice Gembira!
- Kamu menghadiahkan Gryphatrice Gembira!
- Hari %d
- 20 Permata
- Set Ulang Tahun
- Kamu memakai %s
- Temukan Anggota
- Temukan Lebih Banyak Anggota
- Pergi ke Pengaturan
- Lanjutkan Damage
- Keseharian yang terlewat tidak akan menyakitimu
- Rentetan dan penghitung Kebiasaan-mu akan diatur ulang
- Melewatkan Keseharian akan mengulang rentetanmu dan penghitung Kebiasaan akan terulang secara normal
- Kemajuan Misi akan dilanjutkan kembali
- Damage saat ini aktif. Pelajari lebih lanjut.
- Hentikan Damage
- "Keseharian yang terlewat tidak akan menyakitimu "
- Rentetan dan penghitung Kebiasaan-mu tidak akan diatur ulang
- Rentetan Keseharian dan penghitung Kebiasaan hanya akan dihitung saat ini aktif
- Kemajuan Misi akan tetap tertunda
- Daftar
- Melalui Undangan
- Berikut daftar pemain yang ingin bergabung dengan Party
- Tidak ada yang mencari Party sekarang. Kamu bisa kembali lagi nanti!
- Diundang
- Undang dengan @namapengguna atau email
- Nama Pengguna atau alamat email
- Mencari Party\?
- Cari Party
- Kamu sedang mencari Party!
- Perhatikan apakah ada undangan atau mulai Party-mu sendiri kapan saja
- Perlengkapan ini terkunci karena profesi
- Ganti profesi menjadi %s
- Kamu memiliki semua perlengkapan %s
- Perlengkapan baru dirilis selama Gala musiman. Peti Ajaib juga mendapat stok baru setiap bulan!
- Buka perlengkapan dan kemampuan %s
- Batalkan Undangan
- Dibatalkan
- Undangan Tertunda
- Input salah
- Tingkatan Kontributor
- Kunjungi FAQ
- Apakah kamu mau mengganti profesimu ke %s dengan %d permata\?
- Perubahan Belum Disimpan
- Apakah kamu yakin hendak membuang perubahan pada tugas ini\?
- Permata adalah mata uang yang dibeli dengan uang sungguhan yang memungkinkan kamu membeli konten tambahan dalam Habitica dan merupakan salah satu sumber utama dukungan keuangan untuk tim Habitica bersama dengan langganan.
-\n
-\nSemua konten yang dibeli melalui Permata murni kosmetik atau dapat diperoleh secara gratis seiring waktu.
-\n
-\nKamu juga dapat menerima Permata melalui hadiah dari pemain lain, hadiah Tantangan, berkontribusi ke Habitica, atau berlangganan.
- Telinga Hewan
- Masih ada pertanyaan\?
- Centang semua tugasmu untuk memperoleh hadiah
- Sebuah tugas bisa berupa Kebiasaan, Keseharian, atau Agenda. Lanjutkan menyelesaikan mereka untuk mendapatkan berbagai hadiah!
- Lihat Tugas Pengenalan
- Tetaskan Peliharaanmu
- Tugas ini adalah satu dari %1$d tugas dari Tantangan \"%2$s\". Kamu harus meninggalkan Tantangan untuk menghapus tugas ini.
- Antara %s dan %s, cukup beli paket Permata mana saja dan akumu akan dikirimkan Permata dengan jumlah tersebut. Semakin banyak Permata untuk digunakan, dibagikan, atau disimpan untuk kebutuhan mendatang!
- Pertanyaan Umum
- %1$d / %2$d
- Kamu masih membutuhkan ramuan %s untuk menetaskan hewan peliharaan ini
- Tugas ini adalah satu dari %d Tugas yang merupakan bagian dari Tantangan yang sudah tidak lagi aktif. Apa yang ingin kamu lakukan pada Tugas ini\?
- Dengan mendaftar, kamu menunjukkan bahwa kamu telah membaca dan menyetujui Persyaratan Layanan dan Kebijakan Privasi.
- Mata Uang Pelanggan
- Tugas Pengenalan
- Jam Pasir Mistis adalah mata uang yang amat sangat langka yang dapat kamu peroleh melalui berlangganan Habitica selama 3 bulan beruntun atau lebih. Mata uang ini dapat digunakan di toko Penjelajah Waktu untuk membeli perlengkapan yang telah lampau, peliharaan, tunggangan, background animasi, atau bahkan misi spesial.
-\n
-\nKamu dapat memperoleh hingga 4 Jam Pasir Mistis setiap tahun. Waktu pemberian akan disesuaikan dengan jadwal pembaruan berlangganan kamu. Jam Pasir Mistis akan dikirimkan pada awal bulan baru setelah pembayaran berlangganan terakhir kamu yang memenuhi syarat. Lihat [Berlangganan] untuk keterangan lebih lanjut.
- Ketuk \"Hadiahkan fitur Langganan\" dan ketik nama pengguna akun yang ingin kamu berikan. Kemudian, pilih jangka masa berlangganan yang ingin kamu berikan dan bayar. Akunmu secara otomatis akan diberikan hadiah berlangganan yang serupa.
- Kamu belum memiliki peralatan jenis ini. Kamu dapat membeli peralatan dari pasar menggunakan emas yang kamu peroleh.
- Tidak terhubung
- Kamu akan menerima salah satu dari sepuluh jenis Makanan. Selama acara khusus, makanan normal akan berubah jenis menjadi kue atau permen.
- Hobi + Pekerjaan
- Pembagian Waktu + Tanggung Jawab
- kelima
- Rayakan ulang tahun ke-10 Habitica dengan hadiah dan item eksklusif di bawah ini!
- Ini adalah acara waktu terbatas yang dimulai pada %1$s dan akan berakhir %2$s. Gryphatrice Gembira Edisi Terbatas dan sepuluh Ramuan Penetas Ajaib akan tersedia untuk dibeli selama waktu ini. Hadiah lain yang tercantum di bagian Empat Gratis akan secara otomatis dikirimkan ke semua akun yang aktif dalam 30 hari sebelum hari hadiah dikirim. Akun yang dibuat setelah hadiah dikirim tidak akan dapat mengklaimnya.
- Jubah Pesta
- Kamu dapat menggunakan kemampuan %s dan membeli perlengkapannya dari toko!
- Item ini hanya tersedia untuk profesi tetentu.
-\nKamu bisa memilih profesi setelah level 10.
- Kirim Undangan
- Monster Bos masih akan memberikan damage yang disebabkan
-\noleh Keseharian anggota Party lainnya yang terlewat
- Damage atau item koleksi yang tertunda akan berlaku pada reset hari berikutnya
- Damage ke bos atau item koleksi yang ditemukan akan disimpan sampai kamu melanjutkan damage
- Kirim undangan secara langsung ke pemain yang kamu kenal
- Perlengkapan baru dirilis selama Gala musiman. Sampai saat itu, ada %d perlengkapan di Peti Ajaib bisa ditemukan!
- Hanya boleh berisi huruf a-z, angka, tanda hubung, atau garis bawah
- Jika kamu melihat akun dengan nama tampilan berwarna berserta ikon, itu adalah tingkatan kontributor! Tingkatan diberikan kepada orang-orang yang membantu Habitica, apakah dengan penerjemahan, pengkodean, atau hanya menjadi sosok yang membantu. Semakin tinggi tingkatannya, semakin banyak pemain itu berkontribusi.
- Latar Belakang
- Damage saat ini dijeda.
- Ingin bergabung Party dengan orang lain tetapi tidak kenal siapapun\? Beri tahu para pemimpin Party bahwa kamu menunggu untuk diundang!
- Beli Gryphatrice Gembira seharga %d Permata\?
- Monster Bos masih akan memberikan damage yang disebabkan oleh Keseharian anggota Party lainnya yang terlewat
-
- tiap %d hari
-
- Terbaru
- Penyesuaian Rambut Depan Avatar
- Penyesuaian Gaya Rambut Avatar
- Tampilan
- Penyesuaian Warna Rambut Avatar
- Set ini mencakup semua janggut dan kumis
- Tidak bisa dihapus
- Kamu tidak dapat menghapus akun saat memiliki langganan aktif. Harap batalkan langganan terlebih dahulu.
- Pergi ke langganan
- Kamu membutuhkan lebih banyak Permata untuk membeli ini!
- Pembelian Permata mendukung tim kecil kami di Habitica
- Bulan Rilis
- Kata sandi salah
- Ini adalah **%1$s** Tugas yang jatuh tempo **%2$s**.
- Ini adalah **%1$s** Tugas yang tidak memiliki tanggal jatuh tempo.
- Ini adalah **%1$s** Tugas yang berulang **%2$s%3$s**.
- positif dan negatif
- Ditugaskan kepada
- Selesai pada %s
- Gryphatrice Gembira yang langka bergabung dengan pesta ulang tahun! Jangan lewatkan kesempatamu untuk memiliki Peliharaan animasi eksklusif ini.
- Batalkan pemblokiran pengguna
- Batalkan pembisuan Pengguna
- Status
- Akses Biasa
- Pesan ditandai %d kali.
- Google Pay
- PayPal
- Stripe
- Ditugaskan kepadamu oleh @%1$s pada %2$s
- tidak pernah
- pada %s
- pada hari kerja
- pada akhir pekan
- Banyak Ramuan
- Empat Gratis
- Beli seharga %s
- Beli seharga
- Kami membawa kembali 10 Ramuan Penetas Ajaib terfavorit di komunitas. Pergilah ke Pasar untuk melengkapi koleksimu!
- Agar pesta tetap berjalan, kami akan membagikan Jubah Pesta, 20 Permata, dan set Cape edisi terbatas dan Latar Belakang!
- Kunjungi Pasar
- Item dan hadiah eksklusif menantimu
- Berakhir dalam %s
- Lihat Selengkapnya
- Buka Pengaturan
- Batal
- Beli Perlengkapan
- Kamu mendapatkan <b>5 Pencapaian</b> dan <font color=#EE9109><b>100 Koin Emas</b></font> atas usahamu.
- Kotak centang Agenda
- Untuk membuat Guild, masuk ke situs Habitica dan ketuk tombol \"Buat\" pada layar \"Guild Saya\".
- Kamh harus membeli item sebelumnya dalam urutan ini untuk membuka kunci
- Horee!
- Selesaikan tugas untuk mendapatkan makanan! Kamu dapat memberi makan peliharaanmu dari Peliharaan & Tunggangan
- Selesaikan tugas untuk mendapatkan Ramuan Penetas dan Telur kemudian tetaskan peliharaanmu!
- Ramuan Penguat
- Hapus %d Tugas
- Mekanika Game
- Tambahkan tugas untuk hal yang ingin kamu tuntaskan pekan ini
- Isi survei 5 menit ini untuk membantu kami berkembang dan terimalah pencapaian!
- 3 bulan langganan tidak berulang
- Kamu hanya perlu %1$d %2$s untuk menetaskan semua hewan peliharaan jenis ini. Apakah kamu yakin ingin membeli %3$d\?
- Klaim %d Permata
- Baca Lebih Lanjut
- Nonaktifkan Pesan Pribadi
- 1 bulan langganan tidak berulang
- Biasanya %d Permata
- Kamu sudah memiliki semua yang kamu butuhkan untuk semua hewan peliharaan %1$s. Apakah kamu yakin mau membeli %2$d %3$ss\?
- Perlengkapan dibeli
- Aneh
-
- Kamu, %d lainnya
-
-
- %d orang
-
- Ulang Penghitung
- Sesuaikan Penghitung
- Manajer
- Blokir Pengguna
- Bolehkan pemberitahuan Habitica di Pengaturan aplikasi untuk menerima pengingat
- Notifikasi Dimatikan
- Judul
- positif
- negatif
- Bolehkan pemberitahuan Habitica di Pengaturan aplikasi untuk menerima pemberitahuan push
- Kredit langganan-mu sebanyak +%d bulan akan aktif setelah membatalkan
- Hantaran Jam Pasir berikutnya
- Gunakan Item pada anggota Party>
- Konfirmasi reset
- Kamu mengirim @%1$s %2$s permata.
- Kamu sekarang berlangganan untuk 1 bulan
- Kamu sekarang berlangganan untuk %s bulan
- Kamu mendapatkan %s permata.
- Perkembanganmu
- Buka Item
- Kamu menemukan item baru!
- Kamu masih membutuhkan ramuan %s untuk menetaskan hewan peliharaan ini lagi
- Kamu membutuhkan %1$s dan ramuan %2$s untuk menetaskan hewan peliharaan ini lagi
- Tetaskan Peliharaan
- Batasan
- Lihat Paket Permata
- Mode Gelap
- Mode Tema
- Warna Tema
- Kirimkan Umpan Balik
- Kamu memblokir pengguna ini
- Kamu terpilih sebagai pemenang! Kemenanganmu telah tersimpan di Pencapaian.
- Untuk menghormati musim pemberian kami menghadirkan kembali sebuah promosi yang sangat spesial. Sekarang kapanpun kamu memberikan seseorang berlangganan, kamu akan dapat berlangganan yang serupa secara cuma-cuma!
- Memberikan berlangganan dan dapatkan berlangganan secara gratis sampai %s
- 6 bulan langganan tidak berulang
- Persyaratan Layanan
- %.01f damage terkumpul
- %s tersisa
- Penjualan berakhir dalam %s
- Akunku
- Tujuan Permulaan
- Ganti Profesi
- Tujuan Permulaan
- Mode daftar tugas
- Padatkan
- Tingkat perolehan dari Peti Ajaib
- Penyesuaian Kulit Avatar
- Penyesuaian Baju Avatar
- Penyesuaian Janggut Avatar
- Jadi Teratur
- Tugaskan
- Ubah penerima tugas
- Tugaskan ke…
- Promosikan menjadi Manajer
- Bisukan Diam-diam
- Bisukan Pengguna
- Kamu ingin memblokir pengguna ini\?
- Kamu mau membatalkan pemblokiran pengguna ini\?
- Kamu mau mencabut akses chat untuk pengguna ini\?
- Kamu mau mengembalikan akses obrolan pengguna ini\?
- Apakah kamu ingin membisukan pengguna ini diam-diam\?
- Apakah kamu ingin menghapus pembisuan diam-diam\?
- Hapus Pembisuan Diam-diam
- Dibisukan diam-diam, disembunyikan
-
- setiap %d pekan
-
- %1$s dan %2$s
- pada %s
- pertama
-
- tiap %d tahun
-
- Aktifkan Pemberitahuan
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-it/strings.sidebar.xml b/Habitica/res/values-it/strings.sidebar.xml
index 2f508bc6de..ca653885ec 100644
--- a/Habitica/res/values-it/strings.sidebar.xml
+++ b/Habitica/res/values-it/strings.sidebar.xml
@@ -4,9 +4,7 @@
AbilitàSocialMessaggi
- TavernaSquadra
- GildeSfideInventarioPersonalizzazione dell\'Avatar
@@ -18,4 +16,4 @@
StatisticheAbbonamentoCompra Gemme
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-it/strings.tutorial.xml b/Habitica/res/values-it/strings.tutorial.xml
index 98ae43513a..772435a52c 100644
--- a/Habitica/res/values-it/strings.tutorial.xml
+++ b/Habitica/res/values-it/strings.tutorial.xml
@@ -14,6 +14,5 @@
Per ora è tutto. Se hai bisogno di un ripasso, consulta la sezione FAQ.Le Abilità sono dei poteri con straordinari effetti! Tocca un\'abilità per utilizzarla. Costerà punti Mana (la barra blu), che puoi ottenere usando Habitica ogni giorno e completando le attività. Dai un\'occhiata alle FAQ nel menù per avere maggiori informazioni!Qui è dove tu e i tuoi amici potete motivarvi a vicenda e combattere i mostri con le vostre attività!
- Benvenuto nella Taverna, una chat pubblica per tutte le età! Qui puoi chiacchierare e fare delle domande. Divertiti!Premi sul pulsante grigio per assegnare più punti statistica in una volta, oppure usa le frecce per aggiungerne uno alla volta.
diff --git a/Habitica/res/values-it/strings.xml b/Habitica/res/values-it/strings.xml
index 25cfa85331..c91f58fd8d 100644
--- a/Habitica/res/values-it/strings.xml
+++ b/Habitica/res/values-it/strings.xml
@@ -157,9 +157,7 @@
Studia un artistaLavora su un progetto creativoCompleta un progetto creativo
- Le mie Gilde
- Gilde pubbliche
- Gilda
+ GildaAbbandonaPartecipaElimina
@@ -168,7 +166,6 @@
Aggiungi una nuova etichettaPrivacyScrivi messaggio
- Cerca gildeAssetto da battagliaCostumeTesta
@@ -522,7 +519,6 @@
Nessuna classeQuesto oggetto è disponibile solo per una specifica classe.\nPuoi cambiare la tua classe nelle Impostazioni.Puoi comprare solo equipaggiamento per la Classe a cui appartieni
- Benvenuto nella Taverna! Siediti vicino a noi per fare due chiacchiere, o prenditi una pausa dalle tue attività.Entra nella LocandaLinee guida della communityVisualizza le Linee guida della community
@@ -564,7 +560,6 @@
Ian la Guida MissioneMaga StagionaleAttacco Furia:
- Oddio, non fare attenzione al mostro lì sotto — qui siamo ancora in una zona protetta in cui puoi discutere quando fai una pausa.SegnalaRispondiPremio Sfida
@@ -644,8 +639,6 @@
%1$s Punti Statistica non allocati]]>Oggetti Misteriosi]]>%1$s hai ricevuto un invito per unirti alla Squadra %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Scrivi qui sotto il nome di un amico oppure vai in %s per incontrare potenziali compagni !Sto usando un\'abilità
@@ -679,7 +672,7 @@
Invita nella GildaDescrizione della GildaSet del Mistero
- Ragioni della segnalazione (facoltativo)
+ Ragioni della segnalazione (facoltativo)Segnala %s per violazione:Segnala **SOLO** i messaggi che violano le [Linee Guida della Community](https://habitica.com/static/community-guidelines) and/o i [Termini di Servizio](https://habitica.com/static/terms). Inappropriately reporting a post may give you an infraction. Se la segnalazione è inappropriata, potrai subire una sanzione.Classe %s
@@ -1137,7 +1130,7 @@
Aggiunta autenticazione %s%s copiato negli appuntiLa vendita termina fra %s
- %.01f danno in sospeso
+ %s danno in sospesoScartaVuoi cambiare la tua classe in %s per %d gemme\?Completa questi obbiettivi e guadagnerai <b>5 Medaglie</b> e <font color=#EE9109><b>100 Ori</b></font> quando avrai finito!
diff --git a/Habitica/res/values-iw/strings.sidebar.xml b/Habitica/res/values-iw/strings.sidebar.xml
index a218a2aa37..7c665f5753 100644
--- a/Habitica/res/values-iw/strings.sidebar.xml
+++ b/Habitica/res/values-iw/strings.sidebar.xml
@@ -3,9 +3,7 @@
משימותמיומניויותחברתי
- פונדקחבורה
- גילדותאתגריםמלאי ציודדמות
diff --git a/Habitica/res/values-iw/strings.tutorial.xml b/Habitica/res/values-iw/strings.tutorial.xml
index 2c2c1c5d7f..882cd1f039 100644
--- a/Habitica/res/values-iw/strings.tutorial.xml
+++ b/Habitica/res/values-iw/strings.tutorial.xml
@@ -2,5 +2,4 @@
מיומנויות הן יכולות מיוחדות בעלות השפעה עוצמתית! לחצו על מיומנות כדי להשתמש בה. היא תעלה לכם מאנה (המד הכחול), שתרוויחו על ידי סימון משימות מידי יום ועל ידי השלמת משימות בחיים האמיתיים. בידקו את השאלות הנפוצות בתפריט למידע נוסף!זה המקום בו אתם וחבריכם יכולים לישמור על עצמכם מחוייבים למטרות ולהלחם במפלצות עם המשימות שלכם!
- ברוכים הבאים לפונדק, צ׳אט פומבי לכל הגילאים! כאן תוכלו לדבר על פרודוקטיביות ולשאול שאלות. תהנו!
diff --git a/Habitica/res/values-iw/strings.xml b/Habitica/res/values-iw/strings.xml
index eccad6e851..18d7727c28 100644
--- a/Habitica/res/values-iw/strings.xml
+++ b/Habitica/res/values-iw/strings.xml
@@ -129,9 +129,7 @@
לימדו אשף של המלאכהעבוד על פרוייקט יצירתיסיים פרוייקט יצירתי
- הגילדות שלי
- גילדות פומביות
- גילדה
+ גילדהעיזבוהצטרפומחיקה
@@ -139,7 +137,6 @@
תיאורפרטיותכיתבו הודעה
- חיפוש אחר גילדותציוד קרבתחפושתראש
@@ -243,4 +240,4 @@
הרשמה דרך פייסבוקתחילת השימושבואו נתחיל
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ja/strings.sidebar.xml b/Habitica/res/values-ja/strings.sidebar.xml
index a1ef7f5ee0..b90982f4f1 100644
--- a/Habitica/res/values-ja/strings.sidebar.xml
+++ b/Habitica/res/values-ja/strings.sidebar.xml
@@ -4,9 +4,7 @@
スキルコミュニティメッセージ
- キャンプ場パーティー
- ギルドチャレンジ所持品アバターカスタマイズ
@@ -18,5 +16,4 @@
ステータス有料プランジェムを買う
- アバターと装備
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ja/strings.tutorial.xml b/Habitica/res/values-ja/strings.tutorial.xml
index d64c512c42..5ba154d9f3 100644
--- a/Habitica/res/values-ja/strings.tutorial.xml
+++ b/Habitica/res/values-ja/strings.tutorial.xml
@@ -14,6 +14,5 @@
これで説明は終わりです。もし説明を思い出したいときは、よくある質問をチェックしましょう。スキルは、強力な効果を持つ特殊能力です! スキルをタップして使いましょう。スキルを使うとマナ (青いバー) が減ります。マナは、毎日チェックして実生活のタスクを達成することで獲得できます。詳しくは、メニューの「よくある質問」をチェックしましょう!ここは、あなたと仲間たちが目標達成のために互いに責任感を持ち続けられる場です。タスクをこなしてモンスターと戦うことができます!
- キャンプ場へようこそ! ここは年齢なんか気にしないオープンなチャットルームです。ここでタスクの進め方について話したり、質問したりできます。楽しんでね!一度にステータスを割り当てるには灰色のボタンを、1つずつ割り振るには矢印をタップして下さい。
diff --git a/Habitica/res/values-ja/strings.xml b/Habitica/res/values-ja/strings.xml
index 1bb2d198cf..ff7dfae4d8 100644
--- a/Habitica/res/values-ja/strings.xml
+++ b/Habitica/res/values-ja/strings.xml
@@ -112,7 +112,7 @@
クエストをはじめる招待を中止するクエストを中断する
- バージョン %1$s (%2$d)
+ バージョン: %1$s (%2$d)ヘルプ・よくある質問わかった!もう一度通知
@@ -157,9 +157,7 @@
手芸を習う創造的なプロジェクトで働く創造的なプロジェクトを完了する
- 所属ギルド
- 公共ギルド
- ギルド
+ ギルドやめる参加する削除
@@ -168,7 +166,6 @@
タグを追加するプライバシーメッセージを書く
- ギルドを検索武装衣装頭
@@ -232,6 +229,7 @@
選ばない%s になります。いいですか?あなたは%s に転職した!
+
クラスを選ぶ戻る選ばないでいい?
@@ -242,7 +240,7 @@
メールで登録済みのユーザー送信
- メンバーを探す
+ 友達を招待するHabiticaをやってる友達がいたら、ユーザー名を入力してパーティーに招待しよう。メールを通じて、友達がHabiticaに登録したら、自動的にあなたのパーティーに招待するよ!招待の追加
@@ -252,8 +250,8 @@
現実でのタスクをこなして、 #Habitica で %1$s%2$s のペットをかえしました!現実でタスクをこなして、 #Habitica で%1$s の乗騎を手に入れました!Play ストアで開く
- 3 ジェム支払ってクラスを変更しますか?
- これにより、ステータス ポイントが返却され、ショップでロックが解除される装備が切り替わり、利用可能なスキルが変更されます。
+ クラスを変更します。いいですか?3 ジェムかかります。
+ 注意: %s クラスの装備を買うことはできなくなります。確認市場タイムトラベラー
@@ -261,7 +259,7 @@
メッセージがありません。公開チャットから他のユーザーにメッセージを送ることができます!友達を招待するとアンロックHabiticaに連続でログインするとアンロック
- アカウントを作成するとアンロックします
+ アカウントを作成するとアンロック%1$sを購入しましたメッセージをクリップボードにコピーしました編集
@@ -302,7 +300,7 @@
チャレンジに参加中通知ありタグあり
- 有料プランに加入することは、小規模なHabiticaの運営チームを支援して、運営を続ける助けになります、ありがとう!
+ 有料プランに加入することで、開発者を支援してHabiticaの運営を援助できますこれらの限定特典を受けるために有料会員になりましょう!ゴールドをジェムに神秘の砂時計
@@ -522,7 +520,6 @@
このアイテムは特定のクラスにのみ利用可能です、\n
クラスはセッティングから変更できます。現在のクラス用の装備しか購入できません
- ロッジにようこそ!腰掛けてチャットをしましょう。宿にチェックインするコミュニティガイドラインコミュニティガイドラインを見る
@@ -564,7 +561,6 @@
クエストガイドのIan季節の魔女怒りの攻撃:
- やれやれ、下の方にいるモンスターは気にしないでください。——あなたが休憩にチャットするにはまだ安全ですから。報告返信するチャレンジの賞品
@@ -652,8 +648,6 @@
%1$sポイントが割り当てできます。]]>新しいミステリーアイテムパーティー「%1$s」に招待されました %2$s
- プライベートギルド「%1$s」に招待されました
- 「%1$s」ギルドに招待されました%1$sクエストに招待されました%dヶ月前1ヶ月前
@@ -678,7 +672,7 @@
GoogleでサインアップHabiticaに定期的にログインする%d回チェックイン
- メンバーを探す
+ 友達を招待する習慣がありませんFacebookでサインアップAppleでサインイン
@@ -693,7 +687,7 @@
現在のフィルターでは表示できるTo Doがありません。現在のフィルターでは表示できる日課がありません。連続実行
- %1$sに%2$sヶ月分のHabitica有料プランを送りました。
+ %sに%sヶ月分のHabitica有料プランを送りました。下から贈りたいジェムの量を選びましょう!友達に下のユーザー名を教えるか、%sに行って未来の仲間たちに出会いましょう!セール
@@ -714,7 +708,7 @@
くり返し%s クラス%sになる
- 報告の理由(任意)
+ 報告の理由(任意)違反として%sを報告する:アップデートが利用可能です:%1$s (%2$d)ギルドを脱退する
@@ -820,7 +814,7 @@
違反を報告する%sにメッセージを投稿して、疑問について仲間のプレイヤーに答えてもらいましょう。チェックリストのテキスト
- %1$sに%2$sヶ月分のHabitica有料プランを送りました。そして、誰かにプレゼントすると同じ分だけ無料で受けられるキャンペーンによって、あなたのアカウントにも同じプランが無料で適用されました!
+ %sに%sヶ月分のHabitica有料プランを送りました。そして、誰かにプレゼントすると同じ分だけ無料で受けられるキャンペーンによって、あなたのアカウントにも同じプランが無料で適用されました!%1$d/%2$d人参加予定残高からパーティーチャットでのあなたへの返信
@@ -991,16 +985,16 @@
プライベートメッセージが無効になりましたプライベートメッセージを無効にするブロックする
- ブロックしたユーザーからはプライベートメッセージを受けとれません。ですが、彼らの投稿はいままでどおり見ることができます。
+ ブロックしたユーザーからはプライベートメッセージを受けとれません。ですが、ブロックしたユーザーがキャンプ場やギルドに書き込んだ書き込みはいままでどおり見ることができます。ブロックはモデレーターや将来モデレーターになるユーザーに対しては使えません。チャレンジのリンク切れやめる、%dタスクを削除するやめる、タスクも削除するこのタスクは「%2$s」チャレンジの%1$dタスクの一部です。このタスクを削除するにはチャレンジを止めてください。
- %1$sのたまごと%2$sたまごがえしの薬を融合させてこのペットを孵化させましょう!
+ %sのたまごと%sたまごがえしの薬を融合させてこのペットを孵化させましょう!このペットの孵化には%sたまごがえしの薬が必要です
- このペットの孵化には%1$sのたまごと%2$sのたまごがえしの薬が必要です
+ このペットの孵化には%sのたまごと%sたまごがえしの薬が必要ですこのペットをもう一度孵化するには%sたまごがえしの薬が必要です
- このペットをもう一度孵化するには%1$sのたまごと%2$sたまごがえしの薬が必要です
+ このペットをもう一度孵化するには%sのたまごと%sたまごがえしの薬が必要ですこのペットをもう一度孵化するには%sのたまごが必要ですこのペットの孵化には%sのたまごが必要です「アイテム」メニューから新しいたまごにたまごがえしの薬を使ってみましょう!
@@ -1037,7 +1031,7 @@
%sから%sの間、いつものようにジェムセットを買うだけであなたのアカウントにキャンペーンのジェムが贈られます。もっとジェムを使ったりシェアしたり新しい機能のリリースに備えてたくわえておいたりしましょう!制限事項機能説明
- %1$sから%2$sへ
+ %sから%sへ通常は%dジェムあなたはまだメッセージを送ることができますが、誰もあなたにメッセージを送ることはできません。設定からメッセージ機能を再びオンに出来ます。%sをブロックしますか?
@@ -1070,7 +1064,7 @@
ダークモードサインアップすることで利用規約とプライバシーポリシーの内容に同意したものとみなします。終了
- ブロックしたユーザーからはプライベートメッセージを受けとれません。ですが、彼らの投稿はいままでどおり見ることができます。
+ ブロックしたユーザーはあなたにプライベートメッセージを送ることができません。ですが、ブロックしたユーザーがキャンプ場やギルドに書き込んだ書き込みはいままでどおり見ることができます。このユーザーをブロックしていますフィードバックを送るこのキャンペーンは季節限定イベントの間のみの実施です。このイベントはの%s21:00時 (12:00 UTC)に始まり、%sの09:00時(00:00 UTC)に終了します。キャンペーンの対象は自分へのジェムを購入したときにのみ適用されます。
@@ -1127,7 +1121,7 @@
利用規約プライバシーポリシーすべてのタスクを完了しました。すばらしい!
- %.01f ダメージを保留中
+ %s ダメージを保留中回数を修正カウンターをリセット残り%s
@@ -1136,7 +1130,7 @@
パーティーメンバーにアイテムを使う>
保存されていない変更このタスクへの変更を保存せずに終了しますか?
- \@%1$sに%2$s個のジェムを贈りました。
+ %s %s のジェムを贈りました。1ヶ月の有料会員ですセールは%sに終了未接続
@@ -1297,4 +1291,4 @@
メンバーリスト未セットマネージャーへの昇格
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ko/strings.sidebar.xml b/Habitica/res/values-ko/strings.sidebar.xml
index a3e717ba51..582522b96f 100644
--- a/Habitica/res/values-ko/strings.sidebar.xml
+++ b/Habitica/res/values-ko/strings.sidebar.xml
@@ -3,9 +3,7 @@
과제스킬소셜
- 주막파티
- 길드도전인벤토리아바타 설정
@@ -18,4 +16,4 @@
능력치구독보석 구매
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ko/strings.tutorial.xml b/Habitica/res/values-ko/strings.tutorial.xml
index c73d0bd3e5..df2d5f0a29 100644
--- a/Habitica/res/values-ko/strings.tutorial.xml
+++ b/Habitica/res/values-ko/strings.tutorial.xml
@@ -14,6 +14,5 @@
이제 다 되었네요. 다시 보고 싶으시면 FAQ를 확인하세요.스킬은 강력한 효과를 가진 능력입니다! 사용하고자 하는 스킬은 터치하면 됩니다. 스킬은 마나 (파란색 바)를 사용하는데, 마나는 매일 출석하고 일상생활의 과제들을 달성하면 채워집니다. 궁금한 점은 FAQ를 참고하세요!이곳에서는 친구들과 함께 서로의 목표에 대한 책임을 공유하고 과제를 달성하면서 함께 몬스터를 무찌를 수 있습니다!
- 주막에 오신 것을 환영합니다! 여기는 누구나 채팅할수있는 채팅방입니다. 묻고싶은 것이 있으면 여기서 물어보세요. 좋은 시간 보내시길 바랍니다!많은 능력치를 한꺼번에 배분하려면 회색 버튼을 누르시고, 한 포인트씩 따로 할당하려면 화살표를 누르세요.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ko/strings.xml b/Habitica/res/values-ko/strings.xml
index c1cc23426a..c043f46ab1 100644
--- a/Habitica/res/values-ko/strings.xml
+++ b/Habitica/res/values-ko/strings.xml
@@ -153,9 +153,7 @@
공예 배우기창의적인 활동 하기창의적인 과제 끝내기
- 나의 길드
- 공개 길드
- 길드
+ 길드나가기가입하기삭제
@@ -164,7 +162,6 @@
새로운 태그 만들기공개여부메시지 작성
- 길드 검색전투 장비의상머리
@@ -530,7 +527,6 @@
이 아이템은 특정 직업에서만 가능합니다.
\n설정에 들어가서 직업을 바꾸실 수 있습니다.현재 직업에 맞는 장비만 구입할 수 있습니다
- 여관에 오신 것을 환영합니다! 여기 앉아서 담소를 나누거나, 과제로부터 벗어나 휴식을 취해보세요.여관에서 머물기커뮤니티 방침커뮤니티 방침 읽어보기
@@ -600,7 +596,6 @@
퀘스트 안내자 이안계절의 여마법사분노의 공격:
- 어흠, 아래에 있는 몬스터에게 관심갖지 말게나 - 쉬는 시간에 이야기를 나눌 수 있을 만큼 이곳은 아직 안전하다네.신고하기답하기도전 보상
@@ -686,7 +681,7 @@
길드에 초대하기길드 설명미스터리 세트
- 신고 이유 (생략 가능)
+ 신고 이유 (생략 가능)%s 을(를) 규정 위반으로 신고하기:해당 글이 [커뮤니티 방침](https://habitica.com/static/community-guidelines)이나 [이용 약관](https://habitica.com/static/terms)을 **위반했을 때만** 신고하세요. 허위 신고는 규정 위반으로 간주됩니다.%s 직업
@@ -712,8 +707,6 @@
과제 제목로컬 인증 더하기%1$s 퀘스트에 초대되었습니다
- %1$s 길드에 초대되었습니다
- %1$s 비공개 길드에 초대되었습니다%1$s 파티에 초대되었습니다 %2$s"새로운 "미스터리 아이템이 있습니다%1$s 개의 배분되지 않은 능력치 포인트가 있습니다
@@ -1149,7 +1142,7 @@
탐색 탭으로 이동하여 참여할만한 것을 찾아보세요!모든 과제를 완료하셨습니다. 잘하셨어요!개인정보보호정책
- %.01f피해 대기 중
+ %s피해 대기 중로그인 방법비밀번호 추가연결
diff --git a/Habitica/res/values-lt/strings.sidebar.xml b/Habitica/res/values-lt/strings.sidebar.xml
index 326b41caaa..869d653b69 100644
--- a/Habitica/res/values-lt/strings.sidebar.xml
+++ b/Habitica/res/values-lt/strings.sidebar.xml
@@ -3,9 +3,7 @@
UžduotysSugebėjimaiBendruomenė
- SmuklėGrupė
- GildijosIššūkiaiInventoriusVeikėjas
diff --git a/Habitica/res/values-lt/strings.tutorial.xml b/Habitica/res/values-lt/strings.tutorial.xml
index 982c86dc40..26bb8d2322 100644
--- a/Habitica/res/values-lt/strings.tutorial.xml
+++ b/Habitica/res/values-lt/strings.tutorial.xml
@@ -2,5 +2,4 @@
Sugebėjimai turi stiprius efektus! Paspausk ant sugebėjimo ir tavo veikėjas jį panaudos. Tai kainos Magijos taškų (mėlyna juosta), kuriuos užsidirbi prisijungęs kiekvieną dieną ir atliekant savo užduotis. DUK meniu gali rasti daugiau informacijos!Tai yra vieta, kur tu ir tavo draugai galite vienas kitą palaikyti ir taip atsakingai siekti savo tikslų kovodami su pabaisomis ir savo užduotimis!
- Sveiki atvykę į Smuklę, viešą, visiem amžiams tinkantį pokalbių kambarį! Čia galite šnekėti apie produktyvumą ir užduoti klausimus vieni kitiems. Sėkmės!
diff --git a/Habitica/res/values-lt/strings.xml b/Habitica/res/values-lt/strings.xml
index 3d4022dbe0..ef82e5a464 100644
--- a/Habitica/res/values-lt/strings.xml
+++ b/Habitica/res/values-lt/strings.xml
@@ -136,9 +136,7 @@
Mokytis amatoDirbti prie kūrybinio projektoBaigti kūrybinį projektą
- Mano Gildijos
- Viešos Gildijos
- Gildija
+ GildijaPaliktiPrisijungtiTrinti
@@ -146,7 +144,6 @@
AprašymasPrivatumasRašyti Žinutę
- Ieškoti gildijųKovinė AprangaKostiumasGalva
diff --git a/Habitica/res/values-night/colors.xml b/Habitica/res/values-night/colors.xml
index 2cd59c3ab3..812638a21e 100644
--- a/Habitica/res/values-night/colors.xml
+++ b/Habitica/res/values-night/colors.xml
@@ -60,9 +60,14 @@
#2B203A@color/yellow_100
+ @color/brand_500@color/green_500
+ @color/gray_400@color/gray_400
+ @color/gray_10
+ @color/gray_50@color/gray_10@color/red_100
+ @color/brand_600
diff --git a/Habitica/res/values-nl/strings.sidebar.xml b/Habitica/res/values-nl/strings.sidebar.xml
index 99ee1a4dab..f45f9f3fec 100644
--- a/Habitica/res/values-nl/strings.sidebar.xml
+++ b/Habitica/res/values-nl/strings.sidebar.xml
@@ -4,9 +4,7 @@
VaardighedenSociaalBerichten
- HerbergGroep
- GildenUitdagingenBoedelAvatar Aanpassingen
@@ -18,4 +16,4 @@
StatistiekenAbonnementEdelstenen Kopen
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-nl/strings.tutorial.xml b/Habitica/res/values-nl/strings.tutorial.xml
index a85bd493ba..8fdcab8acc 100644
--- a/Habitica/res/values-nl/strings.tutorial.xml
+++ b/Habitica/res/values-nl/strings.tutorial.xml
@@ -14,6 +14,5 @@
Dat was het voorlopig. Als je iets vergeet, kijk dan in de FAQ sectie.Vaardigheiden zijn speciale talenten die krachtige effecten hebben! Tik op een vaardigheid om ze te gebruiken. Het kost je Mana (de blauwe balk), dat je kan verdienen door elke dag in te checken en je taken in de echte wereld te voltooien. Kijk in de FAQ in het menu voor meer informatie!Dit is waar jij en je vrienden elkaar verantwoordelijk kunnen houden voor het bereiken van je doelen en je samen monsters kan bevechten met je taken!
- Welkom in de Herberg, een openbare chatroom voor alle leeftijden! Hier kan je praten over productiviteit en vragen stellen. Veel plezier!Tik de grijze knop aan om meerdere punten ineens aan te wijzen, of tik op de pijlen om ze één punt per keer toe te voegen.
diff --git a/Habitica/res/values-nl/strings.xml b/Habitica/res/values-nl/strings.xml
index 7a3e354be3..19fed923cc 100644
--- a/Habitica/res/values-nl/strings.xml
+++ b/Habitica/res/values-nl/strings.xml
@@ -157,9 +157,7 @@
Studeer een meester van het ambachtAan een creatief project werkenEen creatief project afronden
- Mijn Gilden
- Publieke Gilden
- Gilde
+ GildeVerlatenToetredenVerwijderen
@@ -168,7 +166,6 @@
Nieuwe label toevoegenPrivacyBericht schrijven
- Zoek naar gildenGevechtsuitrustingKostuumHoofd
@@ -521,7 +518,6 @@
KlasseloosDit voorwerp is enkel beschikbaar voor een specifieke klasse.\n Je kunt van klasse veranderen bij je Instellingen.Je kunt enkel uitrusting kopen voor je huidige klasse
- Welkom in de Herberg! Kom erbij zitten om te praten of neem een pauze van je taken.Check in bij de HerberggemeenschapsrichtlijnenBekijk de gemeenschapsrichtlijnen
@@ -563,7 +559,6 @@
Ian de queeste-gidsSeizoenstovenaresWoedeaanval:
- Oh jee, schenk geen aandacht aan het monster hier beneden — dit blijft een veilige plaats om te chatten tijdens je pauzes.rapporteerReageerUitdaging Prijs
@@ -628,8 +623,6 @@
%1$s niet toegekende statuspunten]]>Mysterieuze items]]>%1$s je werd uitgenodigd om je aan te sluiten bij de Groep %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Geen BeloningenEr zijn geen To-Do\'s te zien met de huidige filters.
@@ -860,7 +853,7 @@
Nodig uit voor GildeGilde BeschrijvingMysteriesets
- Reden voor melding (optioneel)
+ Reden voor melding (optioneel)%s melden wegens overtreding:Meld **alleen** een bericht dat de [Communityrichtlijnen](https://habitica.com/static/community-guidelines) en/of [Servicevoorwaarden](https://habitica.com/static/terms) schendt. Het ongepast melden van een bericht kan een overtreding opleveren.Je hebt %s een %s-maand Habitica-abonnement gestuurd en hetzelfde abonnement is toegepast op je account voor onze Gift One Get One-promotie!
diff --git a/Habitica/res/values-no/strings.xml b/Habitica/res/values-no/strings.xml
index 6793192341..194ffa4faf 100755
--- a/Habitica/res/values-no/strings.xml
+++ b/Habitica/res/values-no/strings.xml
@@ -140,9 +140,7 @@
Studer en mester i håndverketJobb på kreativt prosjektFullfør kreativt prosjekt
- Mine Laug
- Offentlige Laug
- Laug
+ LaugForlatBli medSlett
@@ -151,7 +149,6 @@
Legg til ny MerkelappPrivatlivSkriv Melding
- Søk etter laugKamputstyrKostymeHode
diff --git a/Habitica/res/values-pl/strings.sidebar.xml b/Habitica/res/values-pl/strings.sidebar.xml
index 8a8811d310..a7550aaa09 100644
--- a/Habitica/res/values-pl/strings.sidebar.xml
+++ b/Habitica/res/values-pl/strings.sidebar.xml
@@ -4,9 +4,7 @@
UmiejętnościSpołecznośćWiadomości
- KarczmaDrużyna
- GildieWyzwaniaEkwipunekPersonalizacja Awatara
@@ -19,4 +17,4 @@
SubskrypcjaZakup KlejnotówAwatar i Wyposażenie
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-pl/strings.tutorial.xml b/Habitica/res/values-pl/strings.tutorial.xml
index b8344dc225..45aca4d664 100644
--- a/Habitica/res/values-pl/strings.tutorial.xml
+++ b/Habitica/res/values-pl/strings.tutorial.xml
@@ -13,10 +13,9 @@
Możesz również robić realne nagrody, na podstawie tego, co Cię motywuje.To wszystko na razie. Jeżeli potrzebujesz przypomnienia, sprawdź sekcję FAQ.Umiejętności to specjalne zdolności, które mają potężne efekty! Stuknij umiejętność, aby jej użyć. To kosztuje Mana (niebieski pasek), którą zyskujesz zaglądając tu codziennie i kończąc zadania w prawdziwym świecie. Aby dowiedzieć się więcej, zaglądnij do FAQ w menu!
- Tutaj Ty i Twoi przyjaciele pilnujecie wzajemnie swoich celów i walczycie z potworami poprzez swoje zadania!
- Witaj w Karczmie - publicznym czacie dla wszystkich grup wiekowych! Możecie tu rozmawiać o produktywności i zadawać pytania. Miłej zabawy!
+ Tutaj Ty i Twoi przyjaciele pilnujecie wzajemnie swoich celów i walczycie z potworami poprzez swoje zadania!Naciśnij szary przycisk by przydzielić dużo punktów statystyk na raz albo naciskaj strzałki by przydzielać je pojedynczo. Witaj w twojej Drużynie! Możesz znaleźć więcej członków z listy graczy szukających Drużynę lub zaprosić znajomych bezpośrednio aby zdobyć Zwój Misji Bazy-lista.
\n
\nAby dowiedzieć się więcej o Drużynach zobacz Pomoc.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-pl/strings.xml b/Habitica/res/values-pl/strings.xml
index 1d2e1d4d8d..127f65174f 100644
--- a/Habitica/res/values-pl/strings.xml
+++ b/Habitica/res/values-pl/strings.xml
@@ -155,9 +155,7 @@
Studiować mistrzostwo rzemiosłaPracować nad twórczym projektemUkończyć twórczy projekt
- Moje Gildie
- Publiczne Gildie
- Gildia
+ GildiaOpuśćDołączUsuń
@@ -166,7 +164,6 @@
Dodaj nowy tagPrywatnośćNapisz wiadomość
- Szukaj gildiiSprzęt BojowyKostiumGłowa
@@ -478,7 +475,6 @@
Bez klasyTen przedmiot jest dostępny tylko dla konkretnej klasy. \nMożesz zmienić swoją klasę w Ustawieniach.Możesz kupić wyposażenie wyłącznie dla swojej obecnej klasy
- Witaj w Karczmie! Usiądź wygodnie na krześle i pogadaj lub zrób sobie przerwę od swoich zadań.Zamelduj się w KarczmieZasady SpołecznościWyświetl Zasady Społeczności
@@ -585,8 +581,6 @@
nieprzydzielone Punkty Atrybutów: %1$s]]>Tajemniczy Przedmiot]]>%1$s zostałeś/aś zaproszony/a do Dryżyny %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Każdy zdobyty poziom daje Ci jeden punkt, który możesz przydzielić do wybranego przez siebie atrybutu. Możesz zrobić to ręcznie lub pozwolić, by gra zdecydowała za Ciebie, używając jednej z opcji automatycznego przydzielania.Rozwój postaci
@@ -664,7 +658,6 @@
Możesz wybrać, czy chcesz zostawić zadania z Wyzwania na swojej tablicy, czy też usunąć je, gdy opuścisz WyzwanieUtworzono wyzwanieNagroda za Wyzwanie
- Och, skarbie, nie zwracaj uwagi na poniższego potwora - to wciąż jest bezpieczna kryjówka na prowadzenia rozmów podczas przerw.Zajrzyj do Tawerny aby zobaczyć postępy Bossa i jego wskaźnik WściekłościMożesz kontynuować zwykłą misję z Bossami, obrażenia będą zadawane z obu stronNie dostaniesz obrażeń od Bossa za niewykonane zadania, ale za to wzrośnie jego wskaźnik Wściekłości. Jeśli pasek cały się wypełni, Boss zaatakuje jednego ze sprzedawców!
@@ -721,7 +714,7 @@
Wszystkich zalogowańOstatnie zalogowanieTajemnicze zestawy
- Przyczyna zgłoszenia (opcjonalnie)
+ Przyczyna zgłoszenia (opcjonalnie)Zgłoś %s za naruszenie:Zgłaszaj **wyłącznie** post który narusza [Standardy Społeczności](https://habitica.com/static/community-guidelines) i/lub [Warunki usługi](https://habitica.com/static/terms). Niewłaściwe zgłoszenie postu może przynieść Ci naganę.Porażka
diff --git a/Habitica/res/values-pt-rBR/strings.sidebar.xml b/Habitica/res/values-pt-rBR/strings.sidebar.xml
index 3b72a3a293..75cd9fc734 100644
--- a/Habitica/res/values-pt-rBR/strings.sidebar.xml
+++ b/Habitica/res/values-pt-rBR/strings.sidebar.xml
@@ -4,9 +4,7 @@
HabilidadesSocialMensagens
- TavernaGrupo
- GuildasDesafiosInventárioPersonalização do Avatar
@@ -18,5 +16,4 @@
AtributosAssinaturaComprar Gemas
- Avatar & Equipamento
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-pt-rBR/strings.tutorial.xml b/Habitica/res/values-pt-rBR/strings.tutorial.xml
index 12800a0722..77f4f3e133 100644
--- a/Habitica/res/values-pt-rBR/strings.tutorial.xml
+++ b/Habitica/res/values-pt-rBR/strings.tutorial.xml
@@ -14,9 +14,5 @@
Isso é tudo por enquanto. Se você precisa de um lembrete, cheque a seção de Perguntas Frequentes.Habilidades são especiais e têm efeitos poderosos! Selecione uma Habilidade para usá-la. Isso vai consumir Mana (a barra azul), que você consegue usando o Habitica todos os dias e completando suas tarefas da vida real. Confira as Perguntas Frequentes no menu para mais informações!É aqui que você e seus amigos podem se ajudar em seus objetivos e lutar contra monstros usando suas tarefas!
- Seja bem-vindo(a) à Taverna, uma sala de bate-papo pública e para todas as idades! Aqui você pode conversar sobre produtividade e fazer perguntas. Divirta-se!Toque no botão cinza para alocar todos seus atributos de uma vez ou toque nas setas para adicionar um ponto de cada vez.
- Boas vindas ao seu Grupo! Você pode encontrar mais membros a partir de uma lista de jogadores que estão procurando por um Grupo ou convidar amigos diretamente para ganhar o Pergaminho de Missão Basi-lista.
-\n
-\nVisite o Suporte para saber mais sobre Grupos.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-pt-rBR/strings.xml b/Habitica/res/values-pt-rBR/strings.xml
index 7f089afa0e..8959df66b4 100644
--- a/Habitica/res/values-pt-rBR/strings.xml
+++ b/Habitica/res/values-pt-rBR/strings.xml
@@ -157,9 +157,7 @@
Estudar um guruTrabalhar em um projeto criativoTerminar um projeto criativo
- Minhas Guildas
- Guildas Públicas
- Guildas
+ GuildasSairEntrarExcluir
@@ -168,7 +166,6 @@
Adicionar nova etiquetaPrivacidadeEscrever Mensagem
- Procurar GuildasEquipamento de BatalhaTrajeCabeça
@@ -219,7 +216,7 @@
\n • As tarefas não vão perder progresso e os contadores dos Hábitos não irão resetar
\n • Seu dano ao chefão ou items de Missão de coleta ficarão acumulados até você voltar
Você não tem nenhum %s
- Nv. %1$d %2$s
+ Nível %1$d %2$sGuerreiro(a)LadinoMago(a)
@@ -232,6 +229,7 @@
Decidir mais tardeVocê tem certeza que quer ser um(a) %s\?Você é um(a) %s agora!
+
Escolha a sua ClasseVoltarVocê tem certeza de que ainda não quer escolher\?
@@ -242,7 +240,7 @@
Por e-mailConvide usuários existentesEnviar
- Encontrar membros
+ Convidar amigosSe você já tem amigos usando o Habitica, convide-os pelo nome de usuário aqui.Se um amigo(a) entrar no Habitica pelo seu e-mail, ele(a) será automaticamente convidado(a) para o seu grupo!Adicionar convites
@@ -252,8 +250,8 @@
Eu acabei de chocar um mascote de %1$s %2$s no #Habitica completando minhas tarefas da vida real!Eu acabei de ganhar um %1$s de montaria no #Habitica completando minhas tarefas da vida real!Abrir na Play Store
- Você quer mudar sua classe por 3 Gemas\?
- Isso irá reembolsar seus pontos de atributos, mudar qual equipamento fica desbloqueado nas lojas e mudar suas habilidades disponíveis.
+ Você tem certeza que quer mudar a sua classe\? Isso irá custar 3 gemas.
+ Aviso: Você não poderá mais comprar equipamentos da classe %s.ConfirmarMercadoViajantes do Tempo
@@ -523,7 +521,6 @@
Sem ClasseEste item está disponível apenas para uma classe específica.\nVocê pode alterar sua classe em Configurações.Você apenas pode comprar equipamento para sua classe atual
- Boas-vindas à Taverna. Puxe uma cadeira e junte-se ao bate-papo.Entrar na PousadaDiretrizes da ComunidadeVeja as Diretrizes da Comunidade
@@ -565,7 +562,6 @@
Ian o Guia das MissõesFeiticeira SazonalAtaque de Fúria:
- Oh parça, não esquenta com o bicho lá embaixo — aqui ainda é seguro pra trocar ideia enquanto descansa.ReportarResponderPrêmio do Desafio
@@ -651,8 +647,6 @@
%1$s Pontos de Atributos não distribuidos]]>Você possui novos Itens Misteriosos%1$s Você foi convidado para entrar em um Grupo %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Como funcionaDê uma assinatura de presente
@@ -790,7 +784,7 @@
Convidar para a GuildaDescrição da GuildaConjuntos Misteriosos
- Motivo para reportar (opcional)
+ Motivo para reportar (opcional)Reportar %s por violação:Reporte uma mensagem **apenas** se esta violar as [Diretrizes da Comunidade](https://habitica.com/static/community-guidelines) e/ou os [Termos de Serviço](https://habitica.com/static/terms). Reportar uma postagem inadequadamente pode lhe dar uma infração.%s Classe
@@ -838,7 +832,7 @@
criar uma conta%d check-insefetue login regularmente no Habitica
- Encontrar membros
+ Convidar amigos(as)Desbloqueie logando no Habitica %d vezes.1 hora restante%d horas restantes
@@ -1037,7 +1031,7 @@
Mensagens Privadas estão desabilitadasDesabilitar Mensagens PrivadasBloquear
- Um usuário bloqueado não pode te enviar Mensagens Privadas, mas você ainda verá suas postagens.
+ Um usuário bloqueado não pode te enviar Mensagens Privadas, mas você ainda verá suas postagens na Taverna ou nas Guildas. Isso não terá efeito se a pessoa for moderadora agora ou no futuro.Bloquear %s\?Desbloquear UsuárioBloquear Usuário
@@ -1090,7 +1084,7 @@
VocêAo se inscrever, você está indicando que leu e concorda com os Termos de Serviço e com a Política de Privacidade.Concluir
- Um usuário bloqueado não pode lhe enviar mensagens privadas, mas você ainda verá suas postagens.
+ Um usuário bloqueado não pode lhe enviar mensagens privadas, mas você ainda verá seus posts na Taverna ou guildas.Você bloqueou este usuárioSubmeter feedbackModo escuro
@@ -1106,7 +1100,7 @@
Tema ColoridoTermos de ServiçoNada aqui ainda
- %.01f de dano pendente
+ %s de dano pendentePolítica de PrivacidadeLoja AbertaEstilo
@@ -1149,7 +1143,7 @@
DesconectarAdicionarDescartar
- Você quer mudar sua classe para %s por %d Gemas\?
+ Você quer mudar sua Classe para %s por %d Gemas\?Use o item em um membro do grupo>
Mudanças não salvasTem certeza que deseja desfazer as mudanças desta tarefa\?
@@ -1259,8 +1253,7 @@
- Você, %d outro
- Você, %d outros
+ VocêVocês, %d outros%1$s e %2$s
@@ -1423,4 +1416,7 @@
Novos equipamentos são lançados durante as Galas sazonais. Até lá, há %d peças de equipamento do Armário Encantado para encontrar!Novos equipamentos são lançados durante as Galas sazonais. O Armário Encantado recebe novos estoques todo mês também!
-
\ No newline at end of file
+
+=======
+
+>>>>>>> 7c1a682deddcd7a91fc9767586a61890c43396c4
diff --git a/Habitica/res/values-pt-rPT/strings.sidebar.xml b/Habitica/res/values-pt-rPT/strings.sidebar.xml
index 884ecc3da6..41de9a7200 100644
--- a/Habitica/res/values-pt-rPT/strings.sidebar.xml
+++ b/Habitica/res/values-pt-rPT/strings.sidebar.xml
@@ -4,9 +4,7 @@
HabilidadesSocialMensagens
- TabernaEquipa
- GuildasDesafiosInventárioAvatar
@@ -18,4 +16,4 @@
AtributosAssinaturaComprar Gemas
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-pt-rPT/strings.tutorial.xml b/Habitica/res/values-pt-rPT/strings.tutorial.xml
index f993adeec6..8b9eacac5f 100644
--- a/Habitica/res/values-pt-rPT/strings.tutorial.xml
+++ b/Habitica/res/values-pt-rPT/strings.tutorial.xml
@@ -2,7 +2,6 @@
Atributos são habilidades especiais que têm efeitos poderosos! Toque num atributo para o usar. Este irá custar Mana (a barra azul), que ganha ao verificar e completar todos os dias as suas tarefas da vida real. Consulte as FAQs no menu para mais informação!Isto é onde você e os seus amigos podem responsabilizar-se mutuamente para os vossos objetivos e lutar contra monstros com as vossas tarefas!
- Bem-vindo ao Tabern, uma sala de conversação pública para todas as idades! Aqui pode conversar sobre a produtividade e colocar questões. Divirta-se!Aqui estamos! Inseri algumas tarefas para ti com base nos teus interesses. Experimenta adicionar algumas por tua conta. Podes editar qualquer tarefa ao tocar o título.Primeiro os Hábitos. Podem ser Hábitos positivos que queres melhorar ou Hábitos negativos que queres deixar.Sempre que realizares um hábito positivo, clica no + para receberes experiência e ouro!
@@ -15,4 +14,4 @@
Se os seus Afazeres precisam de ser realizados até determinado momento, coloque uma data final. Parece que já pode clicar numa — continue!Se se descuidar e fizer um Hábito negativo, carregar no sinal - vai reduzir a vida do seu avatar, para o ajudar a manter a disciplina.Experimente! Pode explorar outros tipos de tarefas com o botão de navegação, no fundo da página.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-pt-rPT/strings.xml b/Habitica/res/values-pt-rPT/strings.xml
index ee201faea7..fd5b8b5cbd 100644
--- a/Habitica/res/values-pt-rPT/strings.xml
+++ b/Habitica/res/values-pt-rPT/strings.xml
@@ -147,9 +147,7 @@
Estudar a mestria de um ofícioTrabalhe num projeto criativoTermine um projeto criativo
- As Minhas Guildas
- Guildas Públicas
- Guilda
+ GuildaSairAderirEliminar
@@ -157,7 +155,6 @@
DescriçãoPrivacidadeEscrever Mensagem
- Procurar por guildasEquipamento de BatalhaTrajeCabeça
@@ -339,7 +336,6 @@
Chefe de MissãoRecolha de MissãoRecompensas do Dono de Missão
- Bem-vindo à Pousada! Puxe uma cadeira para conversar, ou efetue uma pausa das suas tarefas.Alcançaste o Nível %1$d!Por completares os teus objectivos na vida real, subiste de nível e agora estás totalmente curado!Perdeste um Nível. o teu Ouro, e uma peça de Equipamento, mas podes recuperá-los de volta com trabalho árduo! Boa sorte — Vais-te sair bem.
@@ -375,8 +371,6 @@
%1$s Ponto(s) de Atributo por alocar]]>Items Mistério novos]]>%1$s foi convidado a juntar-se à Equipa %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Conquistas EspeciaisConquistas Sazonais
@@ -488,7 +482,6 @@
Prêmio do DesafioResponderReportar
- Ah, querido, não preste atenção ao monstro abaixo - este ainda é um porto seguro para conversar em teu tempo livre.Ataque de Fúria:Feiticeira SazonalIan o Guia de Missões
diff --git a/Habitica/res/values-ro/strings.xml b/Habitica/res/values-ro/strings.xml
index 7ab06ebb56..f31d9df402 100755
--- a/Habitica/res/values-ro/strings.xml
+++ b/Habitica/res/values-ro/strings.xml
@@ -140,9 +140,7 @@
Studiază un o meserieLucrează la un proiect de creativitateFinalizează proiectul de creativitate
- Breslele mele
- Bresle publice
- Breaslă
+ BreaslăPărăseșteAlătură-teȘterge
@@ -151,7 +149,6 @@
Adaugă Etichetă nouăIntimitateScrie Mesaj
- Caută bresleEchipament de LuptăCostumCap
@@ -466,7 +463,6 @@
Fără ClasăAcest obiect este disponibil doar unei anumite clase.\nÎți poți modifica clasa din Setări.Poți achiziționa doar echipament pentru clasa ta actuală
- Bine ai venit la Han! Ia un scaun pentru a purta o discuție, sau ia o pauză de la sarcinile tale. Cazează-te în HanGhidurile ComunitățiiVezi Ghidurile Comunității
diff --git a/Habitica/res/values-ru/strings.sidebar.xml b/Habitica/res/values-ru/strings.sidebar.xml
index 06dec6ed76..2cd83c33b5 100644
--- a/Habitica/res/values-ru/strings.sidebar.xml
+++ b/Habitica/res/values-ru/strings.sidebar.xml
@@ -4,9 +4,7 @@
НавыкиОбщениеСообщения
- ТавернаКоманда
- ГильдииИспытанияИнвентарьПерсонализация аватара
@@ -19,4 +17,4 @@
ПодпискаПриобрести самоцветыАватар и Снаряжение
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ru/strings.tutorial.xml b/Habitica/res/values-ru/strings.tutorial.xml
index 26595010e5..9ac2bc872a 100644
--- a/Habitica/res/values-ru/strings.tutorial.xml
+++ b/Habitica/res/values-ru/strings.tutorial.xml
@@ -14,9 +14,8 @@
На этом все. Если вы что-то забыли, проверьте раздел часто задаваемых вопросов.Навыки — это специальные способности, которые имеют мощные эффекты! Нажмите на навык что бы использовать его. Это будет стоить вам очков маны (синяя шкала), которая пополняется при входе в игру каждый день и завершением задач в реальной жизни. Читайте FAQ в меню, чтобы узнать подробности!Это место где вы и ваши друзья можете помочь друг другу оставаться ответственными на пути к цели и сражаться с монстрами с помощью своих заданий!
- Добро пожаловать в Таверну, где собрались участники всех рас, полов и возрастов! Здесь вы можете болтать или задавать вопросы. Повеселитесь!Нажмите серую кнопку, чтобы распределить все ваши характеристики сразу, или нажмите на стрелки, чтобы добавить им по одному очку за раз.Добро пожаловать в вашу команду! Вы можете найти участников из списка игроков желающих вступить в команду или пригласить друзей чтобы заработать Свиток заданий базового списка.
\n
\nБольше о командах можете прочитать в поддержке.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-ru/strings.xml b/Habitica/res/values-ru/strings.xml
index 36523690f2..64f1eb09b6 100644
--- a/Habitica/res/values-ru/strings.xml
+++ b/Habitica/res/values-ru/strings.xml
@@ -157,9 +157,7 @@
Улучшить навык владения ремесломПоработать над творческим проектомЗакончить творческий проект
- Мои гильдии
- Открытые гильдии
- Гильдия
+ ГильдияПокинутьПрисоединитьсяУдалить
@@ -168,7 +166,6 @@
Добавить новый ТегПриватностьНаписать сообщение
- Поиск для гильдийБоевая экипировкаКостюмГолова
@@ -515,7 +512,6 @@
БесклассовыйЭтот предмет доступен только определенному классу\n Вы можете изменить свой класс в Настройках.Вы можете покупать экипировку только для текущего класса
- Добро пожаловать в Гостиницу! Пододвигайе кресло к чату или возьмите перерыв и отдохните от ваших заданий.Разместиться в гостиницеПравила сообществаПосмотреть правила сообщества
@@ -557,7 +553,6 @@
Квест-мастер ЯнСезонная ЧародейкаАтака при ярости:
- О, дорогуша, не обращай внимания на монстра внизу - это по-прежнему райский уголок, чтобы поболтать на перерывах.ПожаловатьсяОтветитьНаграда за испытание
@@ -651,8 +646,6 @@
%1$s очков]]>Таинственный предмет]]>%1$s Вас пригласили в команду %2$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>Основные достиженияКратко о гильдии
@@ -722,7 +715,7 @@
Пригласить в гильдиюОписание гильдииТаинственные наборы
- Причина (необязательно)
+ Причина (необязательно)Класс персонажа %sСтать %sНужна помощь\?
@@ -1126,7 +1119,7 @@
Условия ИспользованияПолитика КонфиденциальностиВы завершили все ваши задания. Отлично!
- %.01f ожидаемого урона
+ %s ожидаемого уронаСбросить счётчикНастроить счётчикВы уверены, что не хотите сохранять изменения в этой задаче\?
diff --git a/Habitica/res/values-sv/strings.sidebar.xml b/Habitica/res/values-sv/strings.sidebar.xml
index a3b68eabd9..dd16ba942f 100644
--- a/Habitica/res/values-sv/strings.sidebar.xml
+++ b/Habitica/res/values-sv/strings.sidebar.xml
@@ -9,13 +9,11 @@
KaraktärFörrådUtmaningar
- GillenSällskap
- VärdshusMeddelandenSocialFörmågorUppgifterStatistikAffärer
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-sv/strings.xml b/Habitica/res/values-sv/strings.xml
index 2ddbed5346..0bf62180e0 100755
--- a/Habitica/res/values-sv/strings.xml
+++ b/Habitica/res/values-sv/strings.xml
@@ -140,9 +140,7 @@
Studera en mästare inom hantverketArbeta på ett kreativt projektSlutför ett kreativt projekt
- Mina gillen
- Allmänna gillen
- Gille
+ GilleLämnaGå medRadera
@@ -151,7 +149,6 @@
Lägg till taggPrivatSkriv meddelande
- Sök efter gillenStridsutrustningKostymHuvud
diff --git a/Habitica/res/values-th/strings.sidebar.xml b/Habitica/res/values-th/strings.sidebar.xml
index 3d6fe1f901..71531fec36 100644
--- a/Habitica/res/values-th/strings.sidebar.xml
+++ b/Habitica/res/values-th/strings.sidebar.xml
@@ -1,12 +1,10 @@
- กิลด์สมัครสมาชิกซื้อเพชรปาร์ตี้
- โรงเตี๋ยมข้อความสังคมสกิลภารกิจ
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-th/strings.xml b/Habitica/res/values-th/strings.xml
index 30026a6100..cb5bcd9eb8 100755
--- a/Habitica/res/values-th/strings.xml
+++ b/Habitica/res/values-th/strings.xml
@@ -49,4 +49,4 @@
ให้การสมัครสมาชิกเชิญเข้ากิลด์เชิญให้เข้าร่วมปาร์ตี้
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-tr/strings.sidebar.xml b/Habitica/res/values-tr/strings.sidebar.xml
index ff97f90c53..14327c1f4d 100644
--- a/Habitica/res/values-tr/strings.sidebar.xml
+++ b/Habitica/res/values-tr/strings.sidebar.xml
@@ -4,9 +4,7 @@
YeteneklerSosyalMesajlar
- TavernaTakım
- LoncalarMücadelelerEnvanterAvatar Özelleştirme
diff --git a/Habitica/res/values-tr/strings.tutorial.xml b/Habitica/res/values-tr/strings.tutorial.xml
index 0daf4cf9ba..baaa5314a0 100644
--- a/Habitica/res/values-tr/strings.tutorial.xml
+++ b/Habitica/res/values-tr/strings.tutorial.xml
@@ -13,10 +13,9 @@
Seni motive eden şeyleri kullanarak Gerçek hayat ödülleride oluşturabilirsin.Şimdilik bu kadar. Bir hatırlatıcıya ihtiyacın varsa SSS bölümüne bak.Yetenekler güçlü etkileri olan özel becerilerdir! Kullanmak için bir yetenğe bas. Bu Mana ile (mavi çubuk), yani hergün giriş yapmakla ve gerçek hayat görevlerini tamamlamakla kazanarak ödenir. Daha fazla bilgi için menüdeki SSS\'ı kontrol et!
- Burası senin ve arkadaşlarının birbirinizi hedeflerinize sorumlu tutabileceğiniz ve görevlerinizle canavarlarla savaştığınız yer!
- Taverna, yani herkese açık ve her yaşın sohbet odasına hoşgeldin! Burda üretkenlik hakkında sohbet edebilir ve sorular sorabilirsin. Iyi eğlenceler!
+ Burası senin ve arkadaşlarının birbirinizi hedeflerinize sorumlu tutabileceğiniz ve görevlerinizle canavarlarla savaştığınız yer!Niteliklerinin çoğunu tek seferde paylaştırmak için gri butona dokun veya birer eklemek için okları kullan.Takımına hoş geldin! Takım arayan oyuncular listesinden seçerek veya arkadaşlarını davet ederek Basi-Liste Görev tomarını alabilirsin.
\n
\nTakımlar hakkında daha fazla bilgi edinmek için Desteğe gidebilirsin.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-tr/strings.xml b/Habitica/res/values-tr/strings.xml
index e245ffe6fe..f5282a5cd3 100644
--- a/Habitica/res/values-tr/strings.xml
+++ b/Habitica/res/values-tr/strings.xml
@@ -157,9 +157,7 @@
Bir zanaat uzmanlığına çalışYaratıcı bir proje üzerinde çalışYaratıcı projeyi bitir
- Loncalarım
- Açık Loncalar
- Lonca
+ LoncaAyrılKatılSil
@@ -168,7 +166,6 @@
Yeni Etiket ekleGizlilikMesaj Yaz
- Lonca araSavaş EkipmanıKostümKafa
@@ -521,7 +518,6 @@
SınıfsızBu eşya yalnızca belirli bir sınıf için geçerlidir.\nAyarlardan sınıfını değiştirebilirsin.Yalnızca şu anki sınıfın için olan ekipmanları satın alabilirsin
- Hana hoş geldin! Sohbet etmek için bir sandalye çek.Han\'a GirTopluluk KurallarıTopluluk Kuralları\'nı Gör
@@ -563,7 +559,6 @@
Görev Rehberi IanMevsimsel BüyücüÖfke saldırısı:
- Ah canım, aşağıdaki canavarı görmezden gel — burası hala molalarında sohbet etmen için güvenli bir bölge.BildirCevaplaMücadele Ödülü
@@ -651,8 +646,6 @@
%1$s adet dağıtılmamış Nitelik Puanın var.]]>Gizemli Eşyaların var]]>%2$s Takımına davet edildin %1$s]]>
- %1$s adlı özel Loncaya davet edildin]]>
- %1$s adlı Loncaya davet edildin]]>%1$s Görevine davet edildin]]>%d hafta önce1h önce
@@ -790,7 +783,7 @@
Loncaya Davet EtLonca AçıklamasıGizem Setleri
- Bildirme nedeni (tercihen)
+ Bildirme nedeni (tercihen)%s kullanıcısını ihlal nedeniyle bildir:**Sadece** [Topluluk Kuralları](https://habitica.com/static/community-guidelines)\'nı ve/veya [Kullanım Koşulları](https://habitica.com/static/terms)\' nı ihlal eden gönderileri bildirin. Aksi takdirde ceza alabilirsiniz.%s Sınıfı
@@ -946,9 +939,9 @@
Atanan İstatistikAşağıdaki kullanıcı adını bir arkadaşınıza verin veya potansiyel yoldaşlarla tanışmak için %s gidin!Değerli Taş Satışı, bu yılki Sonbahar Galasının en sonunda tekrar gündeme geldi! Bu, her zamankinden daha fazla Mücevher almak için son bir şans, bu yüzden bu süre boyunca stok yapın!
- 29 Ekim ile 2 Kasım arasında, her zamanki gibi herhangi bir Mücevher paketi satın alın ve hesabınıza promosyon miktarda Mücevher yatırılacaktır. Gelecek sürümler için harcayabileceğiniz, paylaşabileceğiniz veya saklayabileceğiniz daha fazla Mücevher!
- Sonbahar Galası tüm hızıyla devam ediyor, bu yüzden Mücevher Satışımı için mükemmel bir zaman olduğunu düşündük! Artık her satın alma işleminde her zamankinden daha fazla Mücevher kazanacaksınız.
- 22 ve 30 Eylül arasında, her zamanki gibi herhangi bir Gem paketi satın alın ve hesabınıza promosyon miktarda Mücevher kredisi aktarılacaktır. Gelecek sürümler için harcayabileceğiniz, paylaşabileceğiniz veya saklayabileceğiniz daha fazla Mücevher!
+ %s ile %s arasında, her zamanki gibi herhangi bir Mücevher paketi satın alın ve hesabınıza promosyon miktarda Mücevher yatırılacaktır. Gelecek sürümler için harcayabileceğiniz, paylaşabileceğiniz veya saklayabileceğiniz daha fazla Mücevher!
+ Sonbahar Galası tüm hızıyla devam ediyor, bu yüzden ilk Mücevher Satışımızı sunmak için mükemmel bir zaman olduğunu düşündük! Artık her satın alma işleminde her zamankinden daha fazla Mücevher kazanacaksınız.
+ %s ve %s arasında, her zamanki gibi herhangi bir Gem paketi satın alın ve hesabınıza promosyon miktarda Mücevher kredisi aktarılacaktır. Gelecek sürümler için harcayabileceğiniz, paylaşabileceğiniz veya saklayabileceğiniz daha fazla Mücevher!Nasıl çalışır%1$s - %2$sGenellikle %d Mücevher
@@ -1050,7 +1043,7 @@
Şimdi olmazSorularınızın bir başka oyuncu tarafından cevaplanması için%s içinde bir mesaj gönderin.Bir Alışkanlık, Günlük İş veya Yapılacak İş
- %.01f hasar bekliyor
+ %s hasar bekliyor%s kaldı%s seni kazanan olarak seçti! Zaferin başarımlarının arasına eklendi.]]>12 aylık tek seferlik abonelik
@@ -1315,4 +1308,4 @@
Pek Çok İksirDördü Bedava%s için Al
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-uk/strings.sidebar.xml b/Habitica/res/values-uk/strings.sidebar.xml
index 8b78dbf7e2..883c5fa388 100644
--- a/Habitica/res/values-uk/strings.sidebar.xml
+++ b/Habitica/res/values-uk/strings.sidebar.xml
@@ -2,9 +2,7 @@
ЗавданняПовідомлення
- ТавернаІнвентар
- ҐільдіїПридбати самоцвітиХлівНовини
@@ -19,4 +17,4 @@
ВипробуванняПро програмуАватар та спорядження
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-uk/strings.tutorial.xml b/Habitica/res/values-uk/strings.tutorial.xml
index 0018cc8498..3683370159 100644
--- a/Habitica/res/values-uk/strings.tutorial.xml
+++ b/Habitica/res/values-uk/strings.tutorial.xml
@@ -13,10 +13,9 @@
Купуйте спорядження для свого аватара за зароблене золото!Ви також можете створювати реальні спеціальні винагороди на основі того, що Вас мотивує.Навички — це особливі здібності, які мають потужний ефект! Торкніться навику, щоб ним скористатися. Це буде коштувати ману (синя смуга), яку Ви поновлюєте, входячи щодня та виконуючи свої реальні завдання. Перегляньте поширені запитання в меню для отримання додаткової інформації!
- Ласкаво просимо до таверни, загальнодоступної кімнати для спілкування всіх віків! Тут Ви можете поспілкуватися про продуктивність та поставити запитання. Веселіться!Торкніться сірої кнопки, щоб розподілити всі характеристики одночасно, або торкніться стрілок, щоб встановити їх по одній за раз.Якщо завдання має бути виконано до певного часу, встановіть дату виконання. Схоже, що Ви можете відзначити одне вже зараз – вперед!Ласкаво просимо вас в команду! Ви можете знайти більше учасників зі списку гравців, які шукають команду, або запросити друзів напряму, щоб отримати сувій квесту Спискозмія.
\n
\nВідвідайте службу підтримки, щоб дізнатися більше про команди.
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-uk/strings.xml b/Habitica/res/values-uk/strings.xml
index 659df6f533..0a09bc0d29 100755
--- a/Habitica/res/values-uk/strings.xml
+++ b/Habitica/res/values-uk/strings.xml
@@ -144,9 +144,7 @@
ПриєднатисьЗупинити пошукГільдія
- Відкриті гільдії
- Мої гільдії
- Завершити творчий проєкт
+ Завершити творчий проєктПопрацювати над творчим проєктомПокращити навик володіння ремесломСкласти речі в шафі
@@ -191,7 +189,6 @@
Акс. для головиГоловний убірНаряд
- Пошук гільдійНаписати повідомленняКонфіденційність1 тиждень тому
@@ -321,7 +318,6 @@
Переглянути правила спiльнотиПравила спiльнотиПоселитись в готель
- Ласкаво просимо в Готель! Підсувайте стілець, щоб поспілкуватися.Ви можете придбати спорядження лише для вашого поточного класуЦей предмет доступний лише для певного класу.
\nВи можете змінити свій клас у налаштуваннях.
@@ -749,7 +745,7 @@
Запросити до гільдіїОпис гільдіїМістичний комплект
- Причина (опціонально)
+ Причина (опціонально)Помітити %s, як порушення за:Будь ласка, повідомляйте **лише** про публікації, які порушують [Правила спільноти](https://habitica.com/static/community-guidelines) і/або [Умови надання послуг](https://habitica.com/static/terms). Неналежне повідомлення про публікацію може призвести до порушення.Клас %s
@@ -794,7 +790,6 @@
Нагорода за ВипробуванняВідповістиПовідомити
- О, друже, не звертай уваги на монстра нижче - це все ще надійний притулок для спілкування на перервах.Атака люті:Сезонна чарівницяКвест-мастер Ян
@@ -1002,9 +997,7 @@
Назва задачіДодати локальну аутентифікаціюСтворити нову команду
- Вас запросили в гільдію %1$sВас запросили на квест %1$s
- Вас запросили приєднатися до приватної гільдії %1$s%1$s Вас запросили до команди %2$sВи відправили %1$s %2$s-місячну підписку Habitica. Ця ж підписка була застосована до вашого облікового запису, завдяки участі в акції «Подаруйте підписку, отримайте підписку»!Виберіть підписку, яку хочете подарувати внизу! Ця покупка не буде продовжена автоматично.
@@ -1129,7 +1122,7 @@
Ви завершили всі свої завдання. Чудова робота!Політика конфіденційностіУмови обслуговування
- %.01f пошкоджень буде нанесено
+ %s пошкоджень буде нанесено%s залишилосьРозпродаж закінчується %sМій акаунт
@@ -1303,4 +1296,4 @@
Підвищення до менеджераЗвичайний доступЯкщо ви бачите акаунт з кольоровим іменем та іконкою, це рівень контриб\'ютора! Рівні надаються людям, які допомагають у роботі Habitica: перекладають, кодують чи просто є корисними іншим чином. Чим вищий рівень, тим більший внесок зробив граве(ць/чиня).
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-vi/strings.sidebar.xml b/Habitica/res/values-vi/strings.sidebar.xml
index 2d06407b8c..2d39172ba4 100644
--- a/Habitica/res/values-vi/strings.sidebar.xml
+++ b/Habitica/res/values-vi/strings.sidebar.xml
@@ -8,9 +8,7 @@
Tùy chỉnh Nhân vật đại diệnHành trangThử thách
- Bang hộiTổ đội
- Quán rượuTin nhắnCộng đồngKĩ năng
diff --git a/Habitica/res/values-vi/strings.tutorial.xml b/Habitica/res/values-vi/strings.tutorial.xml
index 267b087272..163a85eeb9 100644
--- a/Habitica/res/values-vi/strings.tutorial.xml
+++ b/Habitica/res/values-vi/strings.tutorial.xml
@@ -1,7 +1,6 @@
Chạm vào nút màu xám để phân bố nhiều Chỉ số của bạn trong một lần, hoặc chạm vào mũi tên để thêm chúng một điểm một lần.
- Chào mừng tới Quán rượu, một phòng trò chuyện mở cho tất cả mọi lứa tuổi! Ở đây bạn có thể nói về năng suất và hỏi chuyện nhau. Vui vẻ nhé!Kĩ năng là những khả năng đặc biệt có những hiệu ứng mạnh mẽ! Chạm vào một Kĩ năng để sử dụng chúng. Nó sẽ tiêu tốn Mana (thanh màu xanh), thứ mà bạn kiếm được bằng việc đăng nhập hằng ngày và hoàn thành những công việc đời thực của bạn. Xem ở FAQ để có thêm nhiều thông tin!Đó là tất cả rồi đó. Nếu bạn cần được nhắc lại, xem mục FAQ.Bạn còn có thể tạo Phần thưởng Tự chọn ở đời thực dựa vào thứ gì thúc đẩy bạn.
@@ -16,4 +15,4 @@
Đầu tiên là Thói quen. Chúng có thể là Thói quen tốt bạn muốn cải thiện hay Thói quen xấu bạn muốn bỏ.Đây rồi! Tôi đã thêm vào một số Công việc dựa trên sở thích của bạn. Thử thêm một vài cái của bạn đi nào. Bạn có thể chỉnh sửa bất kì Công việc nào bằng việc chạm vào tiêu đề.Đây là nơi bạn và bạn bè của mình có thể giữ tính trách nhiệm cho nhau để tiến tới những mục tiêu và chiến đấu với quái vật bằng những Công việc của bạn!
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-vi/strings.xml b/Habitica/res/values-vi/strings.xml
index 9e92ffbe3f..4731726d00 100755
--- a/Habitica/res/values-vi/strings.xml
+++ b/Habitica/res/values-vi/strings.xml
@@ -128,7 +128,6 @@
Mô tảRiêng tưViết tin nhắn
- Tìm GuildTrang bị Chiến đấuHôm nayVật phẩm
@@ -169,9 +168,7 @@
ĐầuTrang phụcThêm Nhãn Dán mới
- Cộng đồng Bang hội
- Bang hội của tôi
- Học một trò thủ công
+ Học một trò thủ côngHoàn thành dự án của nhómLàm việc nhómKiểm tra lịch trình
@@ -591,7 +588,6 @@
Xem Nội quy Cộng đồngNội quy Cộng đồngĐi vào Quán trọ
- Chào mừng tới Quán trọ! Kéo một cái ghế ra để trò chuyện, hoặc giải lao sau công việc.Bạn chỉ có thể mua trang bị cho chức nghiệp hiện tại của mìnhVật phẩm này chỉ tồn tại với một chức nghiệp nhất định.
\nBạn có thể thay đổi Chức nghiệp của mình từ Cài đặt.
@@ -608,7 +604,6 @@
Giải thưởng Thử tháchTrả lờiBáo cáo
- Ôi trời, đừng chú ý tới con Quái vật ở dưới — đây vẫn là nơi an toàn để trò chuyện trong lúc bạn nghỉ ngơi.Tấn công Cuồng nộ:Pháp sư Theo mùaNgười hướng dẫn Nhiệm vụ Ian
@@ -649,7 +644,7 @@
Thu lạiMô tả Bang hộiBộ Bí ẩn
- Lí do báo cáo (tùy chọn)
+ Lí do báo cáo (tùy chọn)Báo có %s vì vi phạm:**Chỉ** báo cáo một bài viết mà vi phạm [Nội quy Cộng đồng](https://habitica.com/static/community-guidelines) và/hoặc [Điều khoản Dịch vụ](https://habitica.com/static/terms). Báo cáo không hợp lí có thể khiến bạn nhận một cảnh cáo.Chức nghiệp %s
@@ -680,8 +675,6 @@
TạoĐánh bạiBạn đã được mời tham gia vào Nhiệm vụ %1$s
- Bạn đã được mời tham gia vào Bang hội %1$s
- Bạn đã được mời tham gia vào Bang hội kín %1$s%1$s bạn đã được mời tham gia vào Tổ đội %2$sBạn có Vật phẩm Bí ẩn mớiBạn có %1$s Điểm Chỉ số chưa phân bổ
diff --git a/Habitica/res/values-zh-rTW/strings.sidebar.xml b/Habitica/res/values-zh-rTW/strings.sidebar.xml
index 47acf637f9..10774fff17 100644
--- a/Habitica/res/values-zh-rTW/strings.sidebar.xml
+++ b/Habitica/res/values-zh-rTW/strings.sidebar.xml
@@ -4,9 +4,7 @@
技能社交訊息
- 酒館隊伍
- 公會挑戰物品欄頭像自定義
@@ -18,4 +16,4 @@
統計訂閱購買寶石
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-zh-rTW/strings.tutorial.xml b/Habitica/res/values-zh-rTW/strings.tutorial.xml
index 9c6dfc61c8..521fb2eb68 100644
--- a/Habitica/res/values-zh-rTW/strings.tutorial.xml
+++ b/Habitica/res/values-zh-rTW/strings.tutorial.xml
@@ -14,6 +14,5 @@
目前為止就這樣了。如果您想了解更多,請查看FAQ部分。技能是有強大作用的特殊能力!點擊技能使用。使用技能需要消耗魔法值(藍色條),魔力可由每日打卡和完成現實任務來獲得。更多信息請查詢FAQ!在這裡,你和你的朋友可以互相激勵完成目標並通過完成任務組團打BOSS!
- 歡迎來到酒館,這裡是一個公開的不分年齡的聊天室!這裡,可以聊聊心得,問問問題。祝您玩的愉快!按灰色的按鈕可以把所有的能力點用於一次升級某項能力,或是按箭頭以批次分配能力點。
diff --git a/Habitica/res/values-zh-rTW/strings.xml b/Habitica/res/values-zh-rTW/strings.xml
index 4b172eef48..bfad38ed5a 100644
--- a/Habitica/res/values-zh-rTW/strings.xml
+++ b/Habitica/res/values-zh-rTW/strings.xml
@@ -155,9 +155,7 @@
學習以成為藝術能手進行創新項目完成創新項目
- 我的工會
- 公共公會
- 公會
+ 公會離開加入刪除
@@ -166,7 +164,6 @@
添加新標籤隱私寫訊息
- 搜尋公會戰鬥裝備服裝頭部
@@ -508,7 +505,6 @@
無職業這個物品只對特定職業開放。\n你可以在設置改變你的職業。你只能購買當前職業的裝備
- 歡迎來到酒館小棧!拉張椅子坐下聊天,或者暫停任務休息一下。入住客棧社群規範查看社群規範
@@ -578,7 +574,6 @@
這個操作會永久刪除你的賬戶,無法恢復!剩餘或者已經消費的寶石都無法退款,如果你不容置疑,在下方文本框寫“DELETE”。修改使用者名稱把下方的用戶名給一個隊伍成員,這樣他們就能邀請你加入了
- 噢我親愛的老夥計,不用擔心下面的怪物,這裏仍然是一個安全的避風港,來聊聊天吧。你添加任務了嗎?你增加得越多,獎勵就會越多。溪流終將匯聚成大海。今天完成任務了嗎?
@@ -652,8 +647,6 @@
%1$s點未分配屬性點]]>神秘裝備]]>%1$s 您受邀加入%2$s隊伍]]>
- %1$s私人公會]]>
- %1$s您受邀加入]]>%1$s副本]]>沒有每日任務你的篩選中沒有任何習慣。
@@ -846,7 +839,7 @@
邀請加入公會公會描述神秘套裝
- 報告原因(可選)
+ 報告原因(可選)舉報%s違反:你**只能**舉報違反了【社會準則】(https://habitica.com/static/community-guidelines)和/或者【服務條款】(https://habitica.com/static/terms)的消息。不當的舉報會讓你違規。%s職業
@@ -1186,7 +1179,7 @@
更改你的職業成為%s需要%d的寶石,確認更改\?你在衣櫥裏找到了一個稀有裝備!新手任務
- %.01f即將造成的傷害
+ %s即將造成的傷害你在衣櫥搜查,然後找到了食物。那爲什麼在這裏?完成這些任務,即可贏得<b>5項成就</b>和<font color=#EE9109><b>100金幣</b></font>!Habitica 用戶問卷
diff --git a/Habitica/res/values-zh/strings.sidebar.xml b/Habitica/res/values-zh/strings.sidebar.xml
index 3790711831..c9600e8dd4 100644
--- a/Habitica/res/values-zh/strings.sidebar.xml
+++ b/Habitica/res/values-zh/strings.sidebar.xml
@@ -4,9 +4,7 @@
技能社交讯息
- 酒馆队伍
- 公会挑战物品栏头像自定义
@@ -18,4 +16,4 @@
统计订阅购买宝石
-
\ No newline at end of file
+
diff --git a/Habitica/res/values-zh/strings.tutorial.xml b/Habitica/res/values-zh/strings.tutorial.xml
index 3c902da870..e8142a3b83 100644
--- a/Habitica/res/values-zh/strings.tutorial.xml
+++ b/Habitica/res/values-zh/strings.tutorial.xml
@@ -14,6 +14,5 @@
暂时就是这么多了。如果你需要再次提醒,去看看FAQ部分。技能是有强大作用的特殊能力!点击技能来释放它。使用技能需要消耗魔法值(蓝色条),魔力可由每日签到和完成现实任务来获得。更多信息请查询FAQ!在这里,你和你的朋友可以互相督促完成目标并通过完成任务组团打小怪兽!
- 欢迎来到酒馆,这里是一个公开的不分年龄的聊天室!这里,可以聊聊心得,问问问题。祝您玩的愉快!按灰色按钮来一次分配你的属性,或者按箭头一次加一点。
diff --git a/Habitica/res/values-zh/strings.xml b/Habitica/res/values-zh/strings.xml
index 3f8127089e..6b3348abfd 100644
--- a/Habitica/res/values-zh/strings.xml
+++ b/Habitica/res/values-zh/strings.xml
@@ -159,9 +159,7 @@
学习以成为艺术能手进行创新项目完成创新项目
- 我的公会
- 公共公会
- 公会
+ 公会离开加入删除
@@ -170,7 +168,6 @@
添加新标签隐私写消息
- 查找公会战斗装备服装头部
@@ -512,7 +509,6 @@
无职业这个物品只对特定职业开放。\n你可以在设置改变你的职业。你只能购买当前职业的装备
- 欢迎来到客栈!拉张凳子坐下聊天吧。入住客栈社区守则查看社区守则
@@ -577,7 +573,6 @@
在酒馆可以看到Boss攻略进度和怒气伤害打败Boss就能获得特殊奖励!快来从%s的手里拯救Habitica吧!季节女巫
- 噢我亲爱的老伙计,不用担心下面的怪物,这里仍然是一个安全的避风港,来聊聊天吧。挑战类型已加入简短的名称
@@ -652,8 +647,6 @@
%1$s没分配的属性点]]>神秘物品]]>%1$s 你被邀请加入队伍%2$s]]>
- 您收到邀请加入一个私人公会%1$s
- 您收到了加入公会的邀请%1$s你被邀请加入副本%1$s%s职业成为%s
@@ -778,7 +771,7 @@
邀请加入公会公会简介神秘套装
- 报告原因(可选)
+ 报告原因(可选)举报%s违反:你**只能**举报违反了【社会准则】(https://habitica.com/static/community-guidelines)和/或者【服务条款】(https://habitica.com/static/terms)的消息。不当的举报会让你违规。宝石送礼
@@ -1132,7 +1125,7 @@
转职为%s需要%d宝石,是否确定更改?对队伍成员使用物品>
修改未保存
- 即将造成%.01f点伤害
+ 即将造成%s点伤害已将%s复制到剪贴板调整连击次数重置连击次数
@@ -1282,4 +1275,4 @@
未设置你确定想要花费3颗宝石转变你的职业至%1$s吗?任务概要
-
\ No newline at end of file
+
diff --git a/Habitica/res/values/colors.xml b/Habitica/res/values/colors.xml
index 24939ef1d7..d2f0199977 100644
--- a/Habitica/res/values/colors.xml
+++ b/Habitica/res/values/colors.xml
@@ -127,8 +127,13 @@
@color/green_1@color/yellow_1
+ @color/brand_400@color/green_10
+ @color/gray_10@color/gray_200
+ @color/gray_600
+ @color/gray_600@color/gray_700@color/maroon_100
+ @color/brand_400
diff --git a/Habitica/res/values/dimens.xml b/Habitica/res/values/dimens.xml
index c8d580874b..dd366c5489 100644
--- a/Habitica/res/values/dimens.xml
+++ b/Habitica/res/values/dimens.xml
@@ -48,8 +48,8 @@
16sp14sp2dp
- 92dp
- 120dp
+ 82dp
+ 110dp20dp68dp65dp
diff --git a/Habitica/res/values/strings.sidebar.xml b/Habitica/res/values/strings.sidebar.xml
index 0f4224a3dc..39caaa6472 100644
--- a/Habitica/res/values/strings.sidebar.xml
+++ b/Habitica/res/values/strings.sidebar.xml
@@ -4,11 +4,9 @@
SkillsSocialMessages
- TavernPartyPurchase GemsSubscription
- GuildsChallengesInventoryAvatar Customization
diff --git a/Habitica/res/values/strings.tutorial.xml b/Habitica/res/values/strings.tutorial.xml
index ea63d8efec..300acade4f 100644
--- a/Habitica/res/values/strings.tutorial.xml
+++ b/Habitica/res/values/strings.tutorial.xml
@@ -16,6 +16,5 @@
Skills are special abilities that have powerful effects! Tap on a skill to use it. It will cost Mana (the blue bar), which you earn by checking in every day and by completing your real-life tasks. Check out the FAQ in the menu for more info!Welcome to your Party! You can find more members from a list of players looking for a Party or invite friends directly to earn the Basi-List Quest Scroll.\n\nVisit Support to learn more about Parties.This is where you and your friends can hold each other accountable to your goals and fight monsters with your tasks!
- Welcome to the Tavern, a public, all-ages chatroom! Here you can chat about productivity and ask questions. Have fun!Tap the gray button to allocate lots of your stats at once, or tap the arrows to add them one point at a time.
diff --git a/Habitica/res/values/strings.xml b/Habitica/res/values/strings.xml
index 545c504958..f4d00d3e67 100644
--- a/Habitica/res/values/strings.xml
+++ b/Habitica/res/values/strings.xml
@@ -192,9 +192,7 @@
Work on creative projectFinish creative project
- My Guilds
- Public Guilds
- Guild
+ GuildLeaveJoinDelete
@@ -203,7 +201,6 @@
Add new TagPrivacyWrite Message
- Search for guildsBattle GearCostume
@@ -292,17 +289,20 @@
Opting OutAre you sure you want to be a %s?Do you want to change your class to %s for %d Gems?
- You are now a %s!
- You can use %s skills and purchase gear from shops!
+ You\'re a %s!
+ You can now use %s skills and purchase gear from shops. Gain levels to earn stat points you can use to power up your skills.Choose ClassGo Back
- Are you sure you want to Opt Out?
+ Opt out of the class system?
+ This will leave you without an assigned class. You can choose a class again by enabling the class system from Settings at any time.Change your classChange your class and refund your stat points for 3 gems.Enable Class System
+ Select a class to enable skills, stats, and manaChanging Class
+ Choosing ClassBy Email
- Invite Existing Users
+ Invite Existing playersSendFind MembersIf you have friends already using Habitica, invite them by username here.
@@ -314,14 +314,16 @@
I just hatched a %1$s %2$s pet in #Habitica by completing my real-life tasks!I just gained a %1$s mount in #Habitica by completing my real-life tasks!Open Play Store
- Do you want to change your class for 3 Gems?
- Do you want to change your class to %1$s for 3 Gems?
+ Do you want to change class for 3 Gems?
+ Do you want to change your class to %1$s?
+ This will remove your current class and refund all stat points then let you select a new class
+ This will switch which gear is unlocked in shops and change your available skillsThis will refund your stat points, switch which gear is unlocked in shops, and change your available skills.ConfirmMarketTime TravelersSeasonal Shop
- You don\'t have any messages. You can send a user a new message from their public chat messages!
+ You don\'t have any messages. You can send a player a new message from their public chat messages!Unlock by inviting friendsUnlock by logging into Habitica regularlyUnlock by checking into Habitica %d times.
@@ -335,6 +337,9 @@
EditAre you sure?Do you really want to delete?
+ Your Tags
+ Challenge Tags
+ Group TagsChoose Message Recipient
@@ -377,8 +382,9 @@
Become a subscriber to receive these exclusive benefits!Gold for GemsMystic Hourglasses
- Monthly Mystery Items
+ Exclusive Monthly Gear SetsSpecial Subscriber Pet
+ Hang on with 1 HP!Double the DropsSubscriptionsRecurring every %s
@@ -389,12 +395,14 @@
Subscribe now to get an exclusive set now and receive new items every month!Subscribe now to get this %s and receive new items every month!Receive the Royal Purple Jackalope pet when you become a new subscriber.
+ You’ll have the option of a second chance when you run out of HP.Discover even more items in Habitica with a 2x bonus daily drop cap.
- 25 Gem cap
- 30 Gem cap
- 35 Gem cap
- 45 Gem cap
- +%d Mystic Hourglass
+ 25 Gems a month
+ 30 Gems a month
+ 35 Gems a month
+ 45 Gems a month
+ Hourglass in 3 months
+ %d HourglassesPayment methodSubscription"Your +%d months of subscription credit will activate after canceling
@@ -402,7 +410,7 @@
DueCancel SubscriptionYou can cancel your subscription from the Google Play Store app. Any months of Subscription credit will be applied after your recurring Subscription has ended.
- No longer want to subscribe? Due to constraints on mobile payments, you\'ll need to cancel via our website. To do this, tap the button below, log in to your account, tap the User icon in the top right, then go to Subscription. Any months of subscription credit will be applied after your subscription has ended. We\'ll miss you!
+ No longer want to subscribe? Due to constraints on mobile payments, you\'ll need to cancel via our website. To do this, tap the button below, log in to your account, tap the player icon in the top right, then go to Subscription. Any months of subscription credit will be applied after your subscription has ended. We\'ll miss you!Visit Habitica WebsiteCurrent BonusesMonths Subscribed
@@ -428,6 +436,7 @@
RemoveKeepMy Challenges
+ ChallengesOfficialChallengeYou’re not part of any Challenges right now!
@@ -572,7 +581,7 @@
Deleting AccountYou need to complete more tasks before you can afford this item!Not Enough Gold
- You\'ll need more Gems to buy this item!
+ You\'ll need more Gems to buy this!Purchase gemsYou\'ll need more Mystic Hourglasses to buy this item!Get hourglasses
@@ -634,11 +643,10 @@
This item is only available to a specific class.\nYou can change your class from Settings.This item is only available to a specific class.\nYou can select a class after level 10.You can only purchase gear for your current class
- Welcome to the Inn! Pull up a chair to chat.Check into InnCommunity GuidelinesView Community Guidelines
- Habitica tries to create a welcoming environment for users of all ages and backgrounds, especially in public spaces like the Tavern. If you have any questions, please consult our guidelines.
+ Habitica tries to create a welcoming environment for players of all ages and backgrounds. If you have any questions, please consult our guidelines.Helpful LinksView FAQReport a Bug
@@ -677,7 +685,6 @@
Ian the Quest GuideSeasonal SorceressRage attack:
- Oh dear, pay no heed to the monster below — this is still a safe haven to chat on your breaks.ReportReplyChallenge Prize
@@ -767,8 +774,6 @@
%1$s unallocated Stat Points]]>Mystery Items]]>%2$s has invited you to join the party %1$s]]>
- %1$s]]>
- %1$s]]>%1$s]]>DefeatCreate
@@ -795,13 +800,27 @@
New reminderStreakUpdate available: %1$s (%2$d)
+ You can contact us and a member of our team will do their best to help!Post a message in the %s to have your questions answered by a fellow player.Need more help?Become a %s%s Class**Only** report a post that violates the [Community Guidelines](https://habitica.com/static/community-guidelines) and/or [Terms of Service](https://habitica.com/static/terms). Inappropriately reporting a post may give you an infraction.Report %s for violation:
- Reason for report (optional)
+ You should only report a post that violates the [Community Guidelines](https://habitica.com/static/community-guidelines) and/or [Terms of Service](https://habitica.com/static/terms). Submitting a false report is a violation of Habitica\'s Community Guidelines.
+ You should only report a player that violates the [Community Guidelines](https://habitica.com/static/community-guidelines) and/or [Terms of Service](https://habitica.com/static/terms). Submitting a false report is a violation of Habitica\'s Community Guidelines.
+ Report %s?
+ Report Player
+ Reason for report
+ Why are you reporting this message?
+ Why are you reporting this player?
+ %1$s\n@%2$s
+ This will also block @%s
+ Reason for report
+ Report Message
+ %s Reported
+ Report failed, please try again later
+ Report %sMystery SetsGuild DescriptionInvite to Guild
@@ -1006,7 +1025,7 @@
Support Wacky Habitica Questions
- Could not find user
+ Could not find playerAnimal EarsAnimal TailHeadband
@@ -1069,7 +1088,7 @@
Hatch Pet againMagic PotionsMagic Potion
- Use Saddle
+ Give SaddleHatch your PetHatchDelete Challenge Task?
@@ -1083,10 +1102,10 @@
%s Challenge Task%s Pets%s Mounts
- Block User
- Unblock User
+ Block Player
+ Unblock PlayerBlock %s?
- A blocked user cannot send you Private Messages but you will still see their posts.
+ A blocked player cannot send you Private Messages but you will still see their postsBlockDisable Private MessagesPrivate Messages are disabled
@@ -1099,14 +1118,14 @@
The Fall Gala is in full swing so we thought it was the perfect time for a Gem Sale! Now you will get more Gems with each purchase than ever before.View Gem BundlesBetween %s and %s, simply purchase any Gem bundle like usual and your account will be credited with the promotional amount of Gems. More Gems to spend, share, or save for any future releases!
- This promotion only applies during the limited time event. This event starts on %s (12:00 UTC) and will end %s (00:00 UTC). The promo offer is only available when buying Gems for yourself.
+ This promotion only applies during the limited time event. This event starts on %1$s (%2$s) and will end %3$s (%4$s). The promo offer is only available when buying Gems for yourself.The Gem Sale is back to haunt the very end of this year’s Fall Gala! This is one last chance to get more Gems than ever, so stock up while it lasts!Dark ModeTheme ModeTheme ColorSubmit Feedback
- You blocked this user
- A blocked user cannot send you Private Messages but you will still see their posts.
+ You blocked this player
+ A blocked player cannot send you Private Messages but you will still see their postsFinishBy signing up, you are indicating that you have read and agree to the Terms of Service and Privacy Policy.You
@@ -1127,7 +1146,7 @@
Claim %d GemsIn honor of the season of giving we\'re bringing back a very special promotion. Now when you gift somebody a subscription, you get the same sub for yourself for free!Tap ‘Gift a Subscription’ and type in the username of the account you’d like to gift to. From there, pick the sub length you’d like to gift and check out. Your account will automatically be rewarded with the same level of subscription you just gifted.
- This is a limited time event that starts on %s (13:00 UTC) and will end %s (01:00 UTC). This promotion only applies when you gift to another Habitican. If you or your gift recipient already have a subscription, the gifted subscription will add months of credit that will only be used after the current subscription is cancelled or expires.
+ This is a limited time event that starts on %1$s (%2$s) and will end %3$s (%4$s). This promotion only applies when you gift to another Habitican. If you or your gift recipient already have a subscription, the gifted subscription will add months of credit that will only be used after the current subscription is cancelled or expires.Gift a sub and get a sub for free until %s1 month one-time subscription3 month one-time subscription
@@ -1161,7 +1180,7 @@
You completed all your tasks. Well done!Privacy PolicyTerms of Service
- %.01f dmg pending
+ %s Damage pending%s remainingSale ends in %sMy Account
@@ -1233,8 +1252,8 @@
Your Tutorials were resetYou ran out of Health!But you can get them all back with hard work! Good luck—you’ll do great.
- Broken equipment can be repurchased from Rewards
- %d, lose %d Gold, and break a piece of gear…]]>
+ Broken equipment can be repurchased \nfrom Rewards
+ %d, lose %d Gold, and break a piece of gear… You can earn them all back with hard work!]]>Account ResetAvatar Skin CustomizationAvatar Shirt Customization
@@ -1284,18 +1303,18 @@
Promote to ManagerManagerMember List
- Ban User
+ Ban PlayerShadow Mute
- Mute User
- Do you want to ban this user?
- Do you want to unban this user?
- Do you want to revoke chat access for this user?
- Do you want to return chat access for this user?
- Do you want to shadow mute this user?
+ Mute Player
+ Do you want to ban this player?
+ Do you want to unban this player?
+ Do you want to revoke chat access for this player?
+ Do you want to return chat access for this player?
+ Do you want to shadow mute this player?Do you want to remove the shadow mute?
- Unban User
+ Unban PlayerRemove Shadow Mute
- Unmute User
+ Unmute playerStatusRegular AccessMessage flagged %d times.
@@ -1392,6 +1411,63 @@
Contributor TiersIf you see an account with a colored display name and an icon, that’s a contributor tier! Tiers are given to people who help around Habitica, whether it be for translation, coding, or just being helpful. The higher the tier, the more the player has contributed.Visit FAQ
+ No %s
+ Get Quests from leveling up, log in bonuses, or the [Quest Shop](/shops/quests)!
+ Get transformation items during Seasonal Galas and mystery boxes of subscriber gear are delivered at the start of each month!
+ Complete tasks, buy an Armoire, or head over to the [Market](/shops/market) to stock up!
+ Complete tasks, Potion Quests, or head over to the [Market](/shops/market) to stock up!
+ Complete tasks, Egg Quests, or head over to the [Market](/shops/market) to stock up!
+ Not getting the right %s from tasks?
+ Check out the [Market](/shops/market) to buy just the things you need!
+ Check out the [Quest Shop](/shops/quests) to see what’s available!
+ Want more Quests?
+ potions
+ Check out the [Market](/shops/market) to buy just the eggs you need!
+ Check out the [Market](/shops/market) to buy food or a Saddle to instantly raise your Pet!
+ Check out the [Market](/shops/market) to buy standard and seasonal Hatching Potions!
+ Open Profile
+ Customize Avatar
+ Share Avatar
+ Extra Armoire chances
+ Get two chances at new equipment from the Armoire for the price of one
+ Subscribe to open again for free!
+ Get an extra chance at the Armoire each time you buy it with a subscription
+ Subscribers get extra chances at the Armoire and these other benefits!
+ Open again for free!
+ Second chance: Hold on with 1HP!
+ SUB PERK
+ Get a second chance each day to avoid running out of HP with a subscription
+ Subscribers get a second chance at life each day and these other benefits!
+ You already used your second chance today. It’s available again in %s
+ You got a second chance with 1HP!
+ Watch ad to hold on with 1HP!
+ Quest Mechanics
+ Damaging a Boss
+ Complete any type of task or use skills to rack up pending damage! Damage will be applied on your next day reset. Strength affects how much damage you do.
+ Collecting Quest items
+ When on a Collection Quest, complete your tasks for a random chance to find a Quest item. Pending items will be applied on your next day reset. Perception boosts item rates.
+ Bosses lash out for missed Dailies
+ Boss damage is calculated each time a Party member checks in throughout the day. This damage is applied the next time you perform an action that syncs with the server. Boss damage is recorded in your Party’s chat.
+ Pausing damage
+ "If you’re struggling to complete your Dailies, you can pause damage from Settings for a break. This will prevent the Boss from hurting you or others, but also pause your own damage. Other members missed Dailies will still cause damage, so be careful out there! "
+ Rage meters
+ Some difficult bosses have an orange Rage meter under their HP. This meter fills up when participants miss Dailies. When it fills completely, the boss will let out a fearsome attack that does extra damage!
+ Buy Gems with Gold
+ Subscribe to buy Gems with Gold and receive these other exclusive benefits!
+ Each time you open the Armoire, you can open again for free!
+ Earn Hourglasses over time to purchase items in the Time Traveler\'s Shop
+ See more subscription options
+ Your subscription lets you have a second chance each day if you run out of HP
+ Rage
+ Invited to Group Plan
+ \@Mentions in Group Plans
+ Subscribe to hold on with 1HP!
+ Your subscription gives you an extra chance at the Armoire!
+ You got a second chance with 1HP!
+ Your %s broke
+ Subscribe to buy Gems with Gold and receive these other exclusive benefits!
+ Subscribers get Mystic Hourglasses to buy items in the Time Travelers Shop and these other exclusive benefits!
+
You
@@ -1422,4 +1498,9 @@
every other yearevery %d years
+
+ No Items pending
+ %d Item pending
+ %d Items pending
+
diff --git a/Habitica/res/values/styles.habitica.xml b/Habitica/res/values/styles.habitica.xml
index aea22a3217..1a211fa9b4 100644
--- a/Habitica/res/values/styles.habitica.xml
+++ b/Habitica/res/values/styles.habitica.xml
@@ -60,8 +60,8 @@
+
+
@@ -867,8 +867,8 @@
@@ -882,7 +882,7 @@
@color/red_10
diff --git a/Habitica/res/xml/preferences_fragment.xml b/Habitica/res/xml/preferences_fragment.xml
index 47f1fb1b5e..4e244e2405 100644
--- a/Habitica/res/xml/preferences_fragment.xml
+++ b/Habitica/res/xml/preferences_fragment.xml
@@ -145,7 +145,7 @@
android:key="launch_screen"
android:entries="@array/launch_screen_types"
android:entryValues="@array/launch_screen_values"
- android:defaultValue="habits"
+ android:defaultValue="/user/tasks/habits"
android:layout="@layout/preference_child_summary"/>
-
diff --git a/Habitica/res/xml/remote_config_defaults.xml b/Habitica/res/xml/remote_config_defaults.xml
index cb4222c442..4a8b40c7d7 100644
--- a/Habitica/res/xml/remote_config_defaults.xml
+++ b/Habitica/res/xml/remote_config_defaults.xml
@@ -105,10 +105,6 @@
false
-
- enableNewArmoire
- true
- enableArmoireAdsfalse
@@ -117,17 +113,24 @@
enableFaintAdsfalse
+
+ enableArmoireSubs
+ true
+
+
+ enableFaintSubs
+ true
+ enableSpellAdsfalse
-
- hideTavern
+ hideGuildsfalse
- hideGuilds
+ enableReviewPrompti false
@@ -135,4 +138,4 @@
false
-
\ No newline at end of file
+
diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt
index 1377331c3e..95036d5a91 100644
--- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt
+++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/HabiticaTestCase.kt
@@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
@@ -27,7 +27,6 @@ import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.api.HostConfig
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import io.mockk.clearAllMocks
@@ -61,7 +60,6 @@ open class HabiticaTestCase : TestCase() {
val soundManager: SoundManager = mockk(relaxed = true)
val notificationsManager: NotificationsManager = mockk(relaxed = true)
val hostConfig: HostConfig = mockk(relaxed = true)
- val analyticsManager: AnalyticsManager = mockk(relaxed = true)
val maintenanceService: MaintenanceApiService = mockk(relaxed = true)
val tagRepository: TagRepository = mockk(relaxed = true)
val hatchPetUseCase: HatchPetUseCase = mockk(relaxed = true)
@@ -126,7 +124,6 @@ open class HabiticaTestCase : TestCase() {
if (it.returnType == SharedPreferences::class.starProjectedType) assign(it, obj, sharedPreferences)
if (it.returnType == NotificationsManager::class.starProjectedType) assign(it, obj, notificationsManager)
if (it.returnType == HostConfig::class.starProjectedType) assign(it, obj, hostConfig)
- if (it.returnType == AnalyticsManager::class.starProjectedType) assign(it, obj, analyticsManager)
if (it.returnType == MaintenanceApiService::class.starProjectedType) assign(it, obj, maintenanceService)
if (it.returnType == TagRepository::class.starProjectedType) assign(it, obj, tagRepository)
if (it.returnType == FeedPetUseCase::class.starProjectedType) assign(it, obj, feedPetUseCase)
diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragmentTest.kt
index 7027d699bc..376fd1236c 100644
--- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragmentTest.kt
+++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragmentTest.kt
@@ -56,7 +56,6 @@ internal class NavigationDrawerFragmentTest : FragmentTestCase { withDescendant { withText("Purchase Gems") } }.isVisible()
childWith { withDescendant { withText("Subscription") } }.isVisible()
childWith { withDescendant { withText("Party") } }.isVisible()
- childWith { withDescendant { withText("Tavern") } }.isVisible()
childWith { withDescendant { withText("Support") } }.isVisible()
}
}
diff --git a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt
index fcfbee05a0..5cfd1e1eb0 100644
--- a/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt
+++ b/Habitica/src/androidTest/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/StableRecyclerFragmentTest.kt
@@ -5,7 +5,7 @@ import android.view.View
import androidx.fragment.app.testing.launchFragmentInContainer
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.FragmentTestCase
import io.github.kakaocup.kakao.common.views.KView
import io.github.kakaocup.kakao.recycler.KRecyclerItem
@@ -132,7 +132,7 @@ internal class StableRecyclerFragmentTest : FragmentTestCase
@POST("user/revive")
- suspend fun revive(): HabitResponse
+ suspend fun revive(): HabitResponse
@POST("user/class/cast/{skill}")
suspend fun useSkill(
@@ -265,6 +265,9 @@ interface ApiService {
@Body data: Map
): HabitResponse
+ @POST("members/{mid}/flag")
+ suspend fun reportMember(@Path("mid") mid: String, @Body data: Map): HabitResponse
+
@POST("groups/{gid}/chat/seen")
suspend fun seenMessages(@Path("gid") groupId: String): HabitResponse
@@ -392,6 +395,9 @@ interface ApiService {
@POST("debug/add-ten-gems")
suspend fun debugAddTenGems(): HabitResponse
+ @GET("news")
+ suspend fun getNews(): HabitResponse>
+
// Notifications
@POST("notifications/{notificationId}/read")
suspend fun readNotification(@Path("notificationId") notificationId: String): HabitResponse>
@@ -464,8 +470,8 @@ interface ApiService {
@POST("tasks/{taskID}/unassign/{userID}")
suspend fun unassignFromTask(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse
- @POST("hall/heroes/{memberID}")
- suspend fun updateUser(@Path("memberID") memberID: String, @Body updateData: Map): HabitResponse
+ @PUT("hall/heroes/{memberID}")
+ suspend fun updateUser(@Path("memberID") memberID: String, @Body updateData: Map>): HabitResponse
@GET("hall/heroes/{memberID}")
suspend fun getHallMember(@Path("memberID") memberID: String): HabitResponse
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt
index 1237f404fe..ba331ff5d3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/ApiClient.kt
@@ -122,7 +122,7 @@ interface ApiClient {
suspend fun loginApple(authToken: String): UserAuthResponse?
suspend fun sleep(): Boolean?
- suspend fun revive(): User?
+ suspend fun revive(): Items?
suspend fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse?
@@ -165,6 +165,8 @@ interface ApiClient {
suspend fun flagMessage(groupId: String, mid: String, data: MutableMap): Void?
suspend fun flagInboxMessage(mid: String, data: MutableMap): Void?
+ suspend fun reportMember(mid: String, data: Map): Void?
+
suspend fun seenMessages(groupId: String): Void?
suspend fun inviteToGroup(groupId: String, inviteData: Map): List?
@@ -223,6 +225,8 @@ interface ApiClient {
suspend fun debugAddTenGems(): Void?
+ suspend fun getNews(): List?
+
// Notifications
suspend fun readNotification(notificationId: String): List?
suspend fun readNotifications(notificationIds: Map>): List?
@@ -274,7 +278,7 @@ interface ApiClient {
suspend fun getTeamPlanTasks(teamID: String): TaskList?
suspend fun assignToTask(taskId: String, ids: List): Task?
suspend fun unassignFromTask(taskId: String, userID: String): Task?
- suspend fun updateMember(memberID: String, updateData: Map): Member?
+ suspend fun updateMember(memberID: String, updateData: Map>): Member?
suspend fun getHallMember(userId: String): Member?
suspend fun markTaskNeedsWork(taskID: String, userID: String): Task?
suspend fun retrievePartySeekingUsers(page: Int): List?
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt
index ef50e34f93..89ffb71e9b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/SocialRepository.kt
@@ -15,8 +15,6 @@ import io.realm.RealmResults
import kotlinx.coroutines.flow.Flow
interface SocialRepository : BaseRepository {
- fun getPublicGuilds(): Flow>
-
fun getUserGroups(type: String?): Flow>
suspend fun retrieveGroupChat(groupId: String): List?
fun getGroupChat(groupId: String): Flow>
@@ -29,6 +27,8 @@ interface SocialRepository : BaseRepository {
groupID: String? = null
): Void?
+ suspend fun reportMember(memberID: String, data: Map): Void?
+
suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage?
suspend fun deleteMessage(chatMessage: ChatMessage): Void?
@@ -64,9 +64,6 @@ interface SocialRepository : BaseRepository {
leaderCreateChallenge: Boolean?
): Group?
- suspend fun retrieveGroups(type: String): List?
- fun getGroups(type: String): Flow>
-
fun getInboxMessages(replyToUserID: String?): Flow>
suspend fun retrieveInboxMessages(uuid: String, page: Int): List?
suspend fun retrieveInboxConversations(): List?
@@ -85,7 +82,6 @@ interface SocialRepository : BaseRepository {
suspend fun inviteToGroup(id: String, inviteData: Map): List?
suspend fun retrieveMember(userId: String?, fromHall: Boolean = false): Member?
- suspend fun retrieveMemberWithUsername(username: String?, fromHall: Boolean): Member?
suspend fun findUsernames(
username: String,
@@ -121,7 +117,7 @@ interface SocialRepository : BaseRepository {
fun getGroupMemberships(): Flow>
suspend fun blockMember(userID: String): List?
fun getMember(userID: String?): Flow
- suspend fun updateMember(memberID: String, key: String, value: Any?): Member?
+ suspend fun updateMember(memberID: String, data: Map>): Member?
suspend fun retrievePartySeekingUsers(page: Int = 0): List?
suspend fun retrievegroupInvites(id: String, includeAllPublicFields: Boolean): List?
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt
index 7fcb645df2..85372fe6f2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/UserRepository.kt
@@ -5,6 +5,7 @@ import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.Skill
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
+import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.social.Group
@@ -12,6 +13,7 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
+import com.habitrpg.common.habitica.models.Notification
import com.habitrpg.shared.habitica.models.responses.VerifyUsernameResponse
import com.habitrpg.shared.habitica.models.tasks.Attribute
import kotlinx.coroutines.flow.Flow
@@ -25,7 +27,7 @@ interface UserRepository : BaseRepository {
suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User?
- suspend fun revive(): User?
+ suspend fun revive(): Equipment?
suspend fun resetTutorial(): User?
@@ -47,6 +49,9 @@ interface UserRepository : BaseRepository {
suspend fun runCron(tasks: MutableList)
suspend fun runCron()
+ suspend fun getNews(): List?
+ suspend fun getNewsNotification(): Notification?
+
suspend fun readNotification(id: String): List?
suspend fun readNotifications(notificationIds: Map>): List?
suspend fun seeNotifications(notificationIds: Map>): List?
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
index fb48206abd..f8af1b80a6 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.kt
@@ -8,7 +8,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.api.ApiService
import com.habitrpg.android.habitica.api.GSonFactoryCreator
import com.habitrpg.android.habitica.data.ApiClient
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.ContentResult
@@ -39,7 +39,6 @@ import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.api.Server
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.models.HabitResponse
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
import com.habitrpg.common.habitica.models.PurchaseValidationResult
@@ -73,7 +72,6 @@ import javax.net.ssl.SSLException
class ApiClientImpl(
private val converter: Converter.Factory,
override val hostConfig: HostConfig,
- private val analyticsManager: AnalyticsManager,
private val notificationsManager: NotificationsManager,
private val context: Context
) : ApiClient {
@@ -101,10 +99,8 @@ class ApiClientImpl(
override var languageCode: String? = null
private var lastAPICallURL: String? = null
- private var hadError = false
init {
- analyticsManager.setUserIdentifier(this.hostConfig.userID)
buildRetrofit()
}
@@ -147,13 +143,23 @@ class ApiClientImpl(
lastAPICallURL = original.url.toString()
val response = chain.proceed(request)
if (response.isSuccessful) {
+ hideConnectionProblemDialog()
return@addNetworkInterceptor response
} else {
// Modify cache control for 4xx or 5xx range - effectively "do not cache", preventing caching of 4xx and 5xx responses
if (response.code in 400..599) {
- return@addNetworkInterceptor response.newBuilder()
- .header("Cache-Control", "no-store")
- .build()
+ when (response.code) {
+ 404 -> {
+ // The server is returning a 404 error, which means the requested resource was not found.
+ // In this case - we want to actually cache the response, and handle it in the app
+ // to prevent a niche HttpException/potential network crash
+ return@addNetworkInterceptor response
+ }
+
+ else -> {
+ return@addNetworkInterceptor response.newBuilder().header("Cache-Control", "no-store").build()
+ }
+ }
} else {
return@addNetworkInterceptor response
}
@@ -256,13 +262,13 @@ class ApiClientImpl(
showConnectionProblemDialog(R.string.internal_error_api)
}
} else if (JsonSyntaxException::class.java.isAssignableFrom(throwableClass)) {
- analyticsManager.logError("Json Error: " + lastAPICallURL + ", " + throwable.message)
+ Analytics.logError("Json Error: " + lastAPICallURL + ", " + throwable.message)
} else {
- analyticsManager.logException(throwable)
+ Analytics.logException(throwable)
}
}
- override suspend fun updateMember(memberID: String, updateData: Map): Member? {
+ override suspend fun updateMember(memberID: String, updateData: Map>): Member? {
return process { apiService.updateUser(memberID, updateData) }
}
@@ -273,7 +279,7 @@ class ApiClientImpl(
return try {
errorConverter?.convert(errorResponse) as ErrorResponse
} catch (e: IOException) {
- analyticsManager.logError("Json Error: " + lastAPICallURL + ", " + e.message)
+ Analytics.logError("Json Error: " + lastAPICallURL + ", " + e.message)
ErrorResponse()
}
}
@@ -305,19 +311,21 @@ class ApiClientImpl(
showConnectionProblemDialog(context.getString(resourceTitleString), context.getString(resourceMessageString))
}
+ private var erroredRequestCount = 0
private fun showConnectionProblemDialog(
resourceTitleString: String?,
resourceMessageString: String
) {
- hadError = true
+ erroredRequestCount += 1
val application = (context as? HabiticaBaseApplication)
?: (context.applicationContext as? HabiticaBaseApplication)
application?.currentActivity?.get()
- ?.showConnectionProblem(resourceTitleString, resourceMessageString)
+ ?.showConnectionProblem(erroredRequestCount, resourceTitleString, resourceMessageString)
}
private fun hideConnectionProblemDialog() {
- hadError = false
+ if (erroredRequestCount == 0) return
+ erroredRequestCount = 0
val application = (context as? HabiticaBaseApplication)
?: (context.applicationContext as? HabiticaBaseApplication)
application?.currentActivity?.get()
@@ -332,8 +340,7 @@ class ApiClientImpl(
override fun updateAuthenticationCredentials(userID: String?, apiToken: String?) {
this.hostConfig.userID = userID ?: ""
this.hostConfig.apiKey = apiToken ?: ""
- analyticsManager.setUserIdentifier(this.hostConfig.userID)
- AmplitudeManager.setUserID(hostConfig.userID)
+ Analytics.setUserID(hostConfig.userID)
}
override suspend fun getStatus(): Status? = process { apiService.getStatus() }
@@ -435,6 +442,10 @@ class ApiClientImpl(
return process { apiService.getTasks(type, dueDate) }
}
+// override suspend fun reorderTags(type: String, dueDate: String): {
+// return process { apiService.getTasks(type, dueDate) }
+// }
+
override suspend fun unlockPath(path: String): UnlockResponse? {
return process { apiService.unlockPath(path) }
}
@@ -493,7 +504,7 @@ class ApiClientImpl(
override suspend fun sleep(): Boolean? = process { apiService.sleep() }
- override suspend fun revive(): User? = process { apiService.revive() }
+ override suspend fun revive(): Items? = process { apiService.revive() }
override suspend fun useSkill(skillName: String, targetType: String, targetId: String): SkillResponse? {
return process { apiService.useSkill(skillName, targetType, targetId) }
@@ -574,6 +585,10 @@ class ApiClientImpl(
return process { apiService.likeMessage(groupId, mid) }
}
+ override suspend fun reportMember(mid: String, data: Map): Void? {
+ return process { apiService.reportMember(mid, data) }
+ }
+
override suspend fun flagMessage(groupId: String, mid: String, data: MutableMap): Void? {
return process { apiService.flagMessage(groupId, mid, data) }
}
@@ -723,6 +738,10 @@ class ApiClientImpl(
return process { apiService.debugAddTenGems() }
}
+ override suspend fun getNews(): List? {
+ return process { apiService.getNews() }
+ }
+
override suspend fun readNotification(notificationId: String): List? {
return process { apiService.readNotification(notificationId) }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt
index f5ece205bf..ae0e6eab8a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/InventoryRepositoryImpl.kt
@@ -218,8 +218,8 @@ class InventoryRepositoryImpl(
}
override suspend fun feedPet(pet: Pet, food: Food): FeedResponse? {
- val feedResponse = apiClient.feedPet(pet.key ?: "", food.key) ?: return null
- localRepository.feedPet(food.key, pet.key ?: "", feedResponse.value ?: 0, currentUserID)
+ val feedResponse = apiClient.feedPet(pet.key, food.key) ?: return null
+ localRepository.feedPet(food.key, pet.key, feedResponse.value ?: 0, currentUserID)
return feedResponse
}
@@ -299,7 +299,14 @@ class InventoryRepositoryImpl(
}
override suspend fun purchaseItem(purchaseType: String, key: String, purchaseQuantity: Int): Void? {
- return apiClient.purchaseItem(purchaseType, key, purchaseQuantity)
+ val response = apiClient.purchaseItem(purchaseType, key, purchaseQuantity)
+ if (key == "gem") {
+ val user = localRepository.getLiveUser(currentUserID)
+ localRepository.executeTransaction {
+ user?.purchased?.plan?.gemsBought = purchaseQuantity + (user?.purchased?.plan?.gemsBought ?: 0)
+ }
+ }
+ return response
}
override suspend fun togglePinnedItem(item: ShopItem): List? {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt
index 5513377872..bd6c6894d5 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.kt
@@ -49,8 +49,11 @@ class SocialRepositoryImpl(
return localRepository.getMember(userID)
}
- override suspend fun updateMember(memberID: String, key: String, value: Any?): Member? {
- return apiClient.updateMember(memberID, mapOf(key to value))
+ override suspend fun updateMember(
+ memberID: String,
+ data: Map>
+ ): Member? {
+ return apiClient.updateMember(memberID, data)
}
override suspend fun retrievePartySeekingUsers(page: Int): List? {
@@ -93,6 +96,10 @@ class SocialRepositoryImpl(
}
}
+ override suspend fun reportMember(memberID: String, data: Map): Void? {
+ return apiClient.reportMember(memberID, data)
+ }
+
override suspend fun likeMessage(chatMessage: ChatMessage): ChatMessage? {
if (chatMessage.id.isBlank()) {
return null
@@ -201,22 +208,6 @@ class SocialRepositoryImpl(
return apiClient.updateGroup(copiedGroup.id, copiedGroup)
}
- override suspend fun retrieveGroups(type: String): List? {
- val groups = apiClient.listGroups(type) ?: return null
- if ("guilds" == type) {
- val memberships = groups.map {
- GroupMembership(currentUserID, it.id)
- }
- localRepository.saveGroupMemberships(currentUserID, memberships)
- }
- localRepository.save(groups)
- return groups
- }
-
- override fun getGroups(type: String) = localRepository.getGroups(type)
-
- override fun getPublicGuilds() = localRepository.getPublicGuilds()
-
override fun getInboxConversations() = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getInboxConversation(it) }
override fun getInboxMessages(replyToUserID: String?) = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getInboxMessages(it, replyToUserID) }
@@ -263,24 +254,21 @@ class SocialRepositoryImpl(
return if (userId == null) {
null
} else {
- try {
- if (fromHall) {
- apiClient.getHallMember(userId)
- } else {
- apiClient.getMember(UUID.fromString(userId).toString())
+ if (fromHall) {
+ apiClient.getHallMember(userId)
+ } else {
+ try {
+ val uuid = UUID.fromString(userId).toString()
+ apiClient.getMember(uuid)
+ } catch (_: IllegalArgumentException) {
+ apiClient.getMemberWithUsername(userId)
}
- } catch (_: IllegalArgumentException) {
- apiClient.getMemberWithUsername(userId)
}
}
}
override suspend fun retrievegroupInvites(id: String, includeAllPublicFields: Boolean) = apiClient.getGroupInvites(id, includeAllPublicFields)
- override suspend fun retrieveMemberWithUsername(username: String?, fromHall: Boolean): Member? {
- return retrieveMember(username, fromHall)
- }
-
override suspend fun findUsernames(username: String, context: String?, id: String?): List? {
return apiClient.findUsernames(username, context, id)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt
index c5da7118f7..37d577185c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImpl.kt
@@ -1,10 +1,12 @@
package com.habitrpg.android.habitica.data.implementation
-import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.local.TaskLocalRepository
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.interactors.ScoreTaskLocallyInteractor
import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.responses.BulkTaskScoringData
@@ -14,7 +16,6 @@ import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.modules.AuthenticationHandler
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
@@ -38,7 +39,6 @@ class TaskRepositoryImpl(
apiClient: ApiClient,
authenticationHandler: AuthenticationHandler,
val appConfigManager: AppConfigManager,
- val analyticsManager: AnalyticsManager
) : BaseRepositoryImpl(localRepository, apiClient, authenticationHandler), TaskRepository {
private var lastTaskAction: Long = 0
@@ -101,12 +101,14 @@ class TaskRepositoryImpl(
val thisUser = user ?: localRepository.getUser(authenticationHandler.currentUserID ?: "").firstOrNull() ?: return null
// save local task changes
- analyticsManager.logEvent(
+ Analytics.sendEvent(
"task_scored",
- bundleOf(
- Pair("type", task.type),
- Pair("scored_up", up),
- Pair("value", task.value)
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT,
+ mapOf(
+ "type" to (task.type ?: ""),
+ "scored_up" to up,
+ "value" to task.value
)
)
if (res.lvl == 0) {
@@ -176,15 +178,15 @@ class TaskRepositoryImpl(
item.key = key
item.itemType = type
item.userID = user.id
+
+ when (type) {
+ "eggs" -> bgUser.items?.eggs?.add(item)
+ "food" -> bgUser.items?.food?.add(item)
+ "hatchingPotions" -> bgUser.items?.hatchingPotions?.add(item)
+ "quests" -> bgUser.items?.quests?.add(item)
+ }
}
item.numberOwned += 1
- when (type) {
- "eggs" -> bgUser.items?.eggs?.add(item)
- "food" -> bgUser.items?.food?.add(item)
- "hatchingPotions" -> bgUser.items?.hatchingPotions?.add(item)
- "quests" -> bgUser.items?.quests?.add(item)
- else -> ""
- }
}
bgUser.stats?.hp = res.hp
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt
index 9af393c446..4d491066b0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/UserRepositoryImpl.kt
@@ -9,16 +9,19 @@ import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.QuestAchievement
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.inventory.Customization
+import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.responses.SkillResponse
import com.habitrpg.android.habitica.models.responses.UnlockResponse
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.social.GroupMembership
import com.habitrpg.android.habitica.models.tasks.Task
+import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
import com.habitrpg.android.habitica.modules.AuthenticationHandler
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
+import com.habitrpg.common.habitica.models.Notification
+import com.habitrpg.common.habitica.models.notifications.NewStuffData
import com.habitrpg.shared.habitica.models.responses.TaskDirection
import com.habitrpg.shared.habitica.models.tasks.Attribute
import kotlinx.coroutines.Dispatchers
@@ -39,7 +42,6 @@ class UserRepositoryImpl(
authenticationHandler: AuthenticationHandler,
private val taskRepository: TaskRepository,
private val appConfigManager: AppConfigManager,
- private val analyticsManager: AnalyticsManager
) : BaseRepositoryImpl(localRepository, apiClient, authenticationHandler), UserRepository {
companion object {
@@ -98,9 +100,17 @@ class UserRepositoryImpl(
}
}
- override suspend fun revive(): User? {
- apiClient.revive()
- return retrieveUser(false, true)
+ override suspend fun revive(): Equipment? {
+ val items = apiClient.revive()
+ val currentUser = localRepository.getLiveUser(currentUserID)
+ var brokenItem: Equipment? = null
+ if (items != null && currentUser != null) {
+ brokenItem = items.gear?.owned?.filter { it.owned == false }?.firstOrNull { equipment ->
+ currentUser.items?.gear?.owned?.firstOrNull { it.key == equipment.key && it.owned == true } != null
+ }
+ }
+ retrieveUser(false, true)
+ return brokenItem
}
override suspend fun resetTutorial(): User? {
@@ -174,6 +184,22 @@ class UserRepositoryImpl(
runCron(ArrayList())
}
+ override suspend fun getNews(): List? {
+ return apiClient.getNews()
+ }
+
+ override suspend fun getNewsNotification(): Notification? {
+ val baileyNews = apiClient.getNews()
+ val baileyAnnouncement = (baileyNews?.first() as Map<*, *>)["title"] as String
+ val notification = Notification()
+ notification.id = "custom-new-stuff-notification"
+ notification.type = Notification.Type.NEW_STUFF.type
+ val data = NewStuffData()
+ data.title = baileyAnnouncement
+ notification.data = data
+ return notification
+ }
+
override suspend fun readNotification(id: String): List? {
if (lastReadNotification == id) return null
lastReadNotification = id
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt
index 7f712f980b..32fcb9ab2c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/SocialLocalRepository.kt
@@ -10,10 +10,7 @@ import io.realm.RealmResults
import kotlinx.coroutines.flow.Flow
interface SocialLocalRepository : BaseLocalRepository {
- fun getPublicGuilds(): Flow>
-
fun getUserGroups(userID: String, type: String?): Flow>
- fun getGroups(type: String): Flow>
fun getGroup(id: String): Flow
fun saveGroup(group: Group)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt
index 436e5c699f..474865dcd7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmContentLocalRepository.kt
@@ -47,21 +47,6 @@ open class RealmContentLocalRepository(realm: Realm) : RealmBaseLocalRepository(
}
override fun saveWorldState(worldState: WorldState) {
- val tavern = getUnmanagedCopy(
- realm.where(Group::class.java)
- .equalTo("id", Group.TAVERN_ID)
- .findFirst() ?: Group()
- )
- if (!tavern.isManaged) {
- tavern.id = Group.TAVERN_ID
- }
- if (tavern.quest == null) {
- tavern.quest = Quest()
- }
- tavern.quest?.active = worldState.worldBossActive
- tavern.quest?.key = worldState.worldBossKey
- tavern.quest?.progress = worldState.progress
- save(tavern)
save(worldState)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt
index 2dedb73597..a6d5c8885b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/data/local/implementation/RealmSocialLocalRepository.kt
@@ -128,15 +128,6 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
}
}
- override fun getPublicGuilds() = realm.where(Group::class.java)
- .equalTo("type", "guild")
- .equalTo("privacy", "public")
- .notEqualTo("id", Group.TAVERN_ID)
- .sort("memberCount", Sort.DESCENDING)
- .findAll()
- .toFlow()
- .filter { it.isLoaded }
-
@OptIn(ExperimentalCoroutinesApi::class)
override fun getUserGroups(userID: String, type: String?) = realm.where(GroupMembership::class.java)
.equalTo("userID", userID)
@@ -146,7 +137,6 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.flatMapLatest { memberships ->
realm.where(Group::class.java)
.equalTo("type", type ?: "guild")
- .notEqualTo("id", Group.TAVERN_ID)
.`in`(
"id",
memberships.map {
@@ -158,14 +148,6 @@ class RealmSocialLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm)
.toFlow()
}
- override fun getGroups(type: String): Flow> {
- return realm.where(Group::class.java)
- .equalTo("type", type)
- .findAll()
- .toFlow()
- .filter { it.isLoaded }
- }
-
override fun getGroup(id: String): Flow {
return realm.where(Group::class.java)
.equalTo("id", id)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialogExtensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialogExtensions.kt
index be53e70262..6309d4e2d9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialogExtensions.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/AlertDialogExtensions.kt
@@ -13,14 +13,14 @@ fun HabiticaAlertDialog.addOkButton(
fun HabiticaAlertDialog.addCloseButton(
isPrimary: Boolean = false,
- listener: ((DialogInterface, Int) -> Unit)? = null
+ listener: ((HabiticaAlertDialog, Int) -> Unit)? = null
) {
this.addButton(R.string.close, isPrimary, false, true, listener)
}
fun HabiticaAlertDialog.addCancelButton(
isPrimary: Boolean = false,
- listener: ((DialogInterface, Int) -> Unit)? = null
+ listener: ((HabiticaAlertDialog, Int) -> Unit)? = null
) {
this.addButton(R.string.cancel, isPrimary, false, true, listener)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/DateExtensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/DateExtensions.kt
index 1042c46e36..5111966986 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/DateExtensions.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/DateExtensions.kt
@@ -24,6 +24,15 @@ class DateUtils {
cal.set(Calendar.MILLISECOND, 0)
return cal.time
}
+
+ fun isSameDay(date1 : Date, date2 : Date) : Boolean {
+ val cal1 = Calendar.getInstance()
+ val cal2 = Calendar.getInstance()
+ cal1.time = date1
+ cal2.time = date2
+ return cal1[Calendar.DAY_OF_YEAR] == cal2[Calendar.DAY_OF_YEAR] &&
+ cal1[Calendar.YEAR] == cal2[Calendar.YEAR]
+ }
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt
index ec589a6d3b..b39063d097 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt
@@ -18,7 +18,6 @@ import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.habitrpg.android.habitica.BuildConfig
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import java.io.UnsupportedEncodingException
import java.security.MessageDigest
import java.util.Date
@@ -75,7 +74,6 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
DISABLED
}
- private lateinit var analyticsManager: AnalyticsManager
private lateinit var sharedPreferences: SharedPreferences
const val TAG = "AdHandler"
@@ -144,9 +142,8 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
}
}
- fun setup(sharedPrefs: SharedPreferences, analyticsManager: AnalyticsManager) {
+ fun setup(sharedPrefs: SharedPreferences) {
this.sharedPreferences = sharedPrefs
- this.analyticsManager = analyticsManager
for (type in AdType.values()) {
val time = sharedPrefs.getLong("nextAd${type.name}", 0)
@@ -228,10 +225,12 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
}
override fun onUserEarnedReward(rewardItem: RewardItem) {
- analyticsManager.logEvent(
+ Analytics.sendEvent(
"adRewardEarned",
- bundleOf(
- Pair("type", type.name)
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT,
+ mapOf(
+ "type" to type.name
)
)
FirebaseAnalytics.getInstance(activity).logEvent(
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt
deleted file mode 100644
index a88137aad3..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AmplitudeManager.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.habitrpg.android.habitica.helpers
-
-import android.content.Context
-import android.content.SharedPreferences
-import com.amplitude.android.Amplitude
-import com.amplitude.android.Configuration
-import com.amplitude.android.events.Identify
-import com.habitrpg.android.habitica.BuildConfig
-import com.habitrpg.android.habitica.R
-
-object AmplitudeManager {
- var EVENT_CATEGORY_BEHAVIOUR = "behaviour"
- var EVENT_CATEGORY_NAVIGATION = "navigation"
- var EVENT_HITTYPE_EVENT = "event"
- var EVENT_HITTYPE_PAGEVIEW = "pageview"
- var EVENT_HITTYPE_CREATE_WIDGET = "create"
- var EVENT_HITTYPE_REMOVE_WIDGET = "remove"
- var EVENT_HITTYPE_UPDATE_WIDGET = "update"
-
- lateinit var amplitude: Amplitude
-
- @JvmOverloads
- fun sendEvent(
- eventAction: String?,
- eventCategory: String?,
- hitType: String?,
- additionalData: Map? = null
- ) {
- if (BuildConfig.DEBUG) {
- return
- }
- val data = mutableMapOf(
- "eventAction" to eventAction,
- "eventCategory" to eventCategory,
- "hitType" to hitType,
- "status" to "displayed"
- )
- if (additionalData != null) {
- for ((key, value) in additionalData) {
- data.put(key, value)
- }
- }
- if (eventAction != null) {
- amplitude.track(eventAction, data)
- }
- }
-
- fun sendNavigationEvent(page: String) {
- val additionalData = HashMap()
- additionalData["page"] = page
- sendEvent("navigated", EVENT_CATEGORY_NAVIGATION, EVENT_HITTYPE_PAGEVIEW, additionalData)
- }
-
- fun initialize(context: Context) {
- amplitude = Amplitude(
- Configuration(
- context.getString(R.string.amplitude_app_id),
- context
- )
- )
- }
-
- fun identify(sharedPrefs: SharedPreferences) {
- val identify = Identify()
- .setOnce("androidStore", BuildConfig.STORE)
- sharedPrefs.getString("launch_screen", "")?.let {
- identify.set("launch_screen", it)
- }
- amplitude.identify(identify)
- }
-
- fun setUserID(userID: String) {
- amplitude.setUserId(userID)
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt
index ba126646c0..1345467bca 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt
@@ -8,8 +8,8 @@ import com.amplitude.android.Configuration
import com.amplitude.android.events.Identify
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.R
-import com.habitrpg.shared.habitica.BuildConfig
enum class AnalyticsTarget {
AMPLITUDE,
@@ -54,11 +54,15 @@ object Analytics {
data.putAll(additionalData)
}
if (eventAction != null) {
- if (target == null || target == AnalyticsTarget.AMPLITUDE) {
- amplitude.track(eventAction, data)
+ if (this::amplitude.isInitialized) {
+ if (target == null || target == AnalyticsTarget.AMPLITUDE) {
+ amplitude.track(eventAction, data)
+ }
}
- if (target == null || target == AnalyticsTarget.FIREBASE) {
- firebase.logEvent(eventAction, bundleOf(*data.toList().toTypedArray()))
+ if (this::firebase.isInitialized) {
+ if (target == null || target == AnalyticsTarget.FIREBASE) {
+ firebase.logEvent(eventAction, bundleOf(*data.toList().toTypedArray()))
+ }
}
}
}
@@ -66,7 +70,7 @@ object Analytics {
fun sendNavigationEvent(page: String) {
val additionalData = HashMap()
additionalData["page"] = page
- sendEvent("navigated", EventCategory.NAVIGATION, HitType.PAGEVIEW, additionalData)
+ sendEvent("navigated $page", EventCategory.NAVIGATION, HitType.PAGEVIEW, additionalData)
}
fun initialize(context: Context) {
@@ -85,17 +89,28 @@ object Analytics {
sharedPrefs.getString("launch_screen", "")?.let {
identify.set("launch_screen", it)
}
- amplitude.identify(identify)
+ if (this::amplitude.isInitialized) {
+ amplitude.identify(identify)
+ }
}
fun setUserID(userID: String) {
- amplitude.setUserId(userID)
+ if (this::amplitude.isInitialized) {
+ amplitude.setUserId(userID)
+ }
FirebaseCrashlytics.getInstance().setUserId(userID)
- firebase.setUserId(userID)
+ if (this::firebase.isInitialized) {
+ firebase.setUserId(userID)
+ }
}
- fun setUserProperty(identifier: String, value: String) {
- firebase.setUserProperty(identifier, value)
+ fun setUserProperty(identifier: String, value: Any?) {
+ if (this::amplitude.isInitialized) {
+ amplitude.identify(mapOf(identifier to value))
+ }
+ if (this::firebase.isInitialized) {
+ firebase.setUserProperty(identifier, value?.toString())
+ }
}
fun logError(msg: String) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
index f4100dcbd2..3aa66d09a0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AppConfigManager.kt
@@ -151,24 +151,26 @@ class AppConfigManager(contentRepository: ContentRepository?) : com.habitrpg.com
return remoteConfig.getBoolean("enableFaintAds")
}
- fun enableSpellAds(): Boolean {
- return remoteConfig.getBoolean("enableSpellAds")
+ fun enableArmoireSubs(): Boolean {
+ return remoteConfig.getBoolean("enableArmoireSubs")
}
- fun enableNewArmoire(): Boolean {
- return remoteConfig.getBoolean("enableNewArmoire")
+ fun enableFaintSubs(): Boolean {
+ return remoteConfig.getBoolean("enableFaintSubs")
}
- fun hideTavern(): Boolean {
- return remoteConfig.getBoolean("hideTavern")
- }
- fun hideGuilds(): Boolean {
- return remoteConfig.getBoolean("hideGuilds")
+ fun enableSpellAds(): Boolean {
+ return remoteConfig.getBoolean("enableSpellAds")
}
+
fun hideChallenges(): Boolean {
return remoteConfig.getBoolean("hideChallenges")
}
+ fun enableReviewPrompt(): Boolean {
+ return remoteConfig.getBoolean("enableReviewPrompt")
+ }
+
fun getBirthdayEvent(): WorldStateEvent? {
val events = ((worldState?.events as? List) ?: listOf(worldState?.currentEvent))
return events.firstOrNull { it?.eventKey == "birthday10" && it.end?.after(Date()) == true }
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationOpenHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationOpenHandler.kt
index bb5b4f3c2b..729d2f0da0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationOpenHandler.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationOpenHandler.kt
@@ -4,6 +4,7 @@ import android.content.Intent
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
@@ -46,8 +47,9 @@ class NotificationOpenHandler {
}
}
- private fun openPartyScreen() {
- MainNavigationController.navigate(R.id.partyFragment)
+ private fun openPartyScreen(isChatNotification: Boolean = false) {
+ val tabToOpen = if (isChatNotification) 1 else 0
+ MainNavigationController.navigate(R.id.partyFragment, bundleOf("tabToOpen" to tabToOpen))
}
private fun openNoPartyScreen() {
@@ -63,11 +65,7 @@ class NotificationOpenHandler {
}
private fun openGuildDetailScreen(groupID: String?) {
- if (groupID?.isNotEmpty() != true) {
- MainNavigationController.navigate(R.id.guildOverviewFragment)
- } else {
- MainNavigationController.navigate(R.id.guildFragment, bundleOf("groupID" to groupID))
- }
+ MainNavigationController.navigate(R.id.guildFragment, bundleOf("groupID" to groupID))
}
private fun openSettingsScreen() {
@@ -77,7 +75,6 @@ class NotificationOpenHandler {
private fun handleChatMessage(type: String?, groupID: String?) {
when (type) {
"party" -> openPartyScreen()
- "tavern" -> MainNavigationController.navigate(R.id.tavernFragment)
"guild" -> openGuildDetailScreen(groupID)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt
index 0ff9f64a0e..550d422788 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/NotificationsManager.kt
@@ -102,6 +102,7 @@ class MainNotificationsManager : NotificationsManager {
Notification.Type.ACHIEVEMENT_ONBOARDING_COMPLETE.type -> true
Notification.Type.LOGIN_INCENTIVE.type -> true
Notification.Type.NEW_MYSTERY_ITEMS.type -> true
+ Notification.Type.FIRST_DROP.type -> true
else -> false
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
index d348d58c0e..a60879bda4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
@@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
-import androidx.core.os.bundleOf
import androidx.lifecycle.asFlow
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
@@ -32,7 +31,6 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.PurchaseActivity
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.models.IAPGift
@@ -55,7 +53,6 @@ import kotlin.time.toDuration
class PurchaseHandler(
private val context: Context,
- private val analyticsManager: AnalyticsManager,
private val apiClient: ApiClient,
private val userViewModel: MainUserViewModel
) : PurchasesUpdatedListener, PurchasesResponseListener {
@@ -152,17 +149,22 @@ class PurchaseHandler(
billingClientState = BillingClientState.CONNECTING
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
- if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
- billingClientState = BillingClientState.READY
- MainScope().launchCatching {
- queryPurchases()
+ when (billingResult.responseCode) {
+ BillingClient.BillingResponseCode.OK -> {
+ billingClientState = BillingClientState.READY
+ MainScope().launchCatching {
+ queryPurchases()
+ }
+ }
+ BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
+ retryListening()
+ }
+ BillingClient.BillingResponseCode.SERVICE_TIMEOUT -> {
+ retryListening()
+ }
+ else -> {
+ billingClientState = BillingClientState.UNAVAILABLE
}
- } else if (billingResult.responseCode == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) {
- retryListening()
- } else if (billingResult.responseCode == BillingClient.BillingResponseCode.SERVICE_TIMEOUT) {
- retryListening()
- } else {
- billingClientState = BillingClientState.UNAVAILABLE
}
}
@@ -289,7 +291,7 @@ class PurchaseHandler(
if (purchase.purchaseState != Purchase.PurchaseState.PURCHASED || processedPurchases.contains(purchase.orderId)) {
return
}
- processedPurchases.add(purchase.orderId)
+ purchase.orderId?.let { processedPurchases.add(it) }
val sku = purchase.products.firstOrNull()
when {
sku == PurchaseTypes.JubilantGrphatrice -> {
@@ -349,7 +351,6 @@ class PurchaseHandler(
try {
apiClient.validateSubscription(validationRequest)
processedPurchase(purchase)
- analyticsManager.logEvent("user_subscribed", bundleOf(Pair("sku", sku)))
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
acknowledgePurchase(purchase)
}
@@ -499,7 +500,7 @@ class PurchaseHandler(
if (displayedConfirmations.contains(purchase.orderId)) {
return
}
- displayedConfirmations.add(purchase.orderId)
+ purchase.orderId?.let { displayedConfirmations.add(it) }
CoroutineScope(Dispatchers.Main).launchCatching {
val application = (context as? HabiticaBaseApplication)
?: (context.applicationContext as? HabiticaBaseApplication) ?: return@launchCatching
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ReviewManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ReviewManager.kt
new file mode 100644
index 0000000000..4968495662
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/ReviewManager.kt
@@ -0,0 +1,89 @@
+package com.habitrpg.android.habitica.helpers
+
+import android.content.Context
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
+import com.google.android.play.core.review.ReviewManagerFactory
+
+class ReviewManager(context: Context, private val configManager: AppConfigManager) {
+
+ private val reviewManager = ReviewManagerFactory.create(context)
+ private val sharedPref = context.getSharedPreferences("ReviewPrefs", Context.MODE_PRIVATE)
+
+ companion object {
+ private const val REVIEW_REQUEST_COUNT_KEY = "ReviewRequestCount"
+ private const val INITIAL_CHECKINS_KEY = "InitialCheckins"
+ private const val SHOULD_QUEUE_REVIEW = "ShouldQueueReview"
+ private const val LAST_REVIEW_CHECKIN_KEY = "LastReviewCheckin"
+ }
+
+ private fun canRequestReview(currentCheckins: Int): Boolean {
+ if (!configManager.enableReviewPrompt()) return false
+ val initialCheckins = sharedPref.getInt(INITIAL_CHECKINS_KEY, -1)
+ val shouldQueueReview = sharedPref.getBoolean(SHOULD_QUEUE_REVIEW, false)
+ val lastReviewCheckin = sharedPref.getInt(LAST_REVIEW_CHECKIN_KEY, -1)
+
+ if (!shouldQueueReview) {
+ // First review request has been made, wait for following request (if any)
+ // to request again in the spirit of asking during a logical break.
+ sharedPref.edit {
+ putBoolean(SHOULD_QUEUE_REVIEW, true)
+ }
+ }
+
+ if (initialCheckins == -1) {
+ // Store the current checkins as the initial value
+ sharedPref.edit {
+ putInt(INITIAL_CHECKINS_KEY, currentCheckins)
+ }
+ return true
+ }
+
+ val requestCount = sharedPref.getInt(REVIEW_REQUEST_COUNT_KEY, 0)
+
+ if (requestCount >= 5) {
+ // Requested reviews 5 times already, no longer request in-app review
+ return false
+ }
+
+ if (currentCheckins - initialCheckins > 75) {
+ // Player has more than 75 additional check-ins starting from the first request, no longer request in-app review
+ return false
+ }
+
+ if (lastReviewCheckin != -1 && currentCheckins - lastReviewCheckin < 5) {
+ // Less than 5 check-ins since the last review request, wait for more check-ins
+ return false
+ }
+
+ return true
+ }
+
+ fun requestReview(activity: AppCompatActivity, currentCheckins: Int) {
+ if (!canRequestReview(currentCheckins)) return
+
+ val request = reviewManager.requestReviewFlow()
+ request.addOnCompleteListener { task ->
+ if (task.isSuccessful) {
+ val reviewInfo = task.result
+ val flow = reviewManager.launchReviewFlow(activity, reviewInfo)
+ flow.addOnCompleteListener { _ ->
+ // The flow has finished - Increase the request count.
+ incrementReviewRequestCount()
+ }
+ }
+ }
+
+ // Save the current checkins after a successful review request
+ sharedPref.edit {
+ putInt(LAST_REVIEW_CHECKIN_KEY, currentCheckins)
+ }
+ }
+
+ private fun incrementReviewRequestCount() {
+ val currentCount = sharedPref.getInt(REVIEW_REQUEST_COUNT_KEY, 0)
+ sharedPref.edit {
+ putInt(REVIEW_REQUEST_COUNT_KEY, currentCount + 1)
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt
index 9822cf4e91..64a9f7c8d5 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/notifications/PushNotificationManager.kt
@@ -7,7 +7,9 @@ import androidx.core.content.edit
import com.google.firebase.messaging.FirebaseMessaging
import com.google.firebase.messaging.RemoteMessage
import com.habitrpg.android.habitica.data.ApiClient
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.helpers.launchCatching
import kotlinx.coroutines.MainScope
@@ -141,10 +143,10 @@ class PushNotificationManager(
if (remoteMessage.data.containsKey("sendAnalytics")) {
val additionalData = HashMap()
additionalData["identifier"] = remoteMessageIdentifier ?: ""
- AmplitudeManager.sendEvent(
+ Analytics.sendEvent(
"receive notification",
- AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
- AmplitudeManager.EVENT_HITTYPE_EVENT,
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT,
additionalData
)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt
index b6f377caf0..7000f5daa3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/CheckClassSelectionUseCase.kt
@@ -16,20 +16,20 @@ class CheckClassSelectionUseCase @Inject constructor() : UseCase() {
- override suspend fun run(requestValues: FeedPetUseCase.RequestValues): FeedResponse? {
+ override suspend fun run(requestValues: RequestValues): FeedResponse? {
val feedResponse = inventoryRepository.feedPet(requestValues.pet, requestValues.food)
(requestValues.context as? SnackbarActivity)?.showSnackbar(content = feedResponse?.message)
if (feedResponse?.value == -1) {
- val mountWrapper =
- View.inflate(
- requestValues.context,
- R.layout.pet_imageview,
- null
- ) as? FrameLayout
- val mountImageView =
- mountWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView
+ val mountWrapper = MountImageviewBinding.inflate(requestValues.context.layoutInflater)
- mountImageView?.loadImage("Mount_Icon_" + requestValues.pet.key)
+ mountWrapper.mountImageview.setMount(requestValues.pet.key)
+ val currentActivity =
+ HabiticaBaseApplication.getInstance(requestValues.context)?.currentActivity?.get()
val dialog = HabiticaAlertDialog(requestValues.context)
+ if (currentActivity != null) {
+ mountWrapper.backgroundView.setContent {
+ HabiticaTheme {
+ BackgroundScene(Modifier.clip(HabiticaTheme.shapes.large))
+ }
+ }
+ dialog.window?.let {
+ mountWrapper.root.setViewTreeSavedStateRegistryOwner(currentActivity)
+ it.decorView.setViewTreeSavedStateRegistryOwner(currentActivity)
+ mountWrapper.root.setViewTreeLifecycleOwner(currentActivity)
+ it.decorView.setViewTreeLifecycleOwner(currentActivity)
+ }
+ }
dialog.setTitle(
requestValues.context.getString(
R.string.evolved_pet_title,
requestValues.pet.text
)
)
- dialog.setAdditionalContentView(mountWrapper)
+ dialog.isCelebratory = true
+ dialog.setAdditionalContentView(mountWrapper.root)
dialog.addButton(R.string.onwards, true)
dialog.addButton(R.string.share, false) { hatchingDialog, _ ->
val message =
@@ -50,25 +67,11 @@ constructor(
R.string.share_raised,
requestValues.pet.text
)
- val mountImageSideLength = 99
- val sharedImage = Bitmap.createBitmap(
- mountImageSideLength,
- mountImageSideLength,
- Bitmap.Config.ARGB_8888
- )
- val canvas = Canvas(sharedImage)
- mountImageView?.drawable?.setBounds(
- 0,
- 0,
- mountImageSideLength,
- mountImageSideLength
- )
- mountImageView?.drawable?.draw(canvas)
- (requestValues.context as? BaseActivity)?.shareContent(
- "raisedPet",
- message,
- sharedImage
- )
+ MainScope().launchCatching {
+ ShareMountUseCase().callInteractor(ShareMountUseCase.RequestValues(
+ requestValues.pet.key, message, requestValues.context
+ ))
+ }
hatchingDialog.dismiss()
}
dialog.enqueue()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt
index 67a5d4415b..4d7e7e3dcd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/HatchPetUseCase.kt
@@ -1,20 +1,24 @@
package com.habitrpg.android.habitica.interactors
import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
import android.view.View
-import android.widget.FrameLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
+import com.habitrpg.android.habitica.databinding.PetImageviewBinding
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.user.Items
-import com.habitrpg.android.habitica.ui.activities.BaseActivity
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.android.habitica.ui.views.BackgroundScene
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
+import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.launchCatching
-import com.habitrpg.common.habitica.views.PixelArtView
import kotlinx.coroutines.MainScope
import javax.inject.Inject
@@ -24,28 +28,58 @@ constructor(
) : UseCase() {
override suspend fun run(requestValues: RequestValues): Items? {
return inventoryRepository.hatchPet(requestValues.egg, requestValues.potion) {
- val petWrapper = View.inflate(requestValues.context, R.layout.pet_imageview, null) as? FrameLayout
- val petImageView = petWrapper?.findViewById(R.id.pet_imageview) as? PixelArtView
-
- petImageView?.loadImage("stable_Pet-" + requestValues.egg.key + "-" + requestValues.potion.key)
+ val petWrapper = PetImageviewBinding.inflate(requestValues.context.layoutInflater)
+ val petKey = requestValues.egg.key + "-" + requestValues.potion.key
+ petWrapper.petImageview.loadImage("stable_Pet-" + petKey)
val potionName = requestValues.potion.text
val eggName = requestValues.egg.text
+ val currentActivity =
+ HabiticaBaseApplication.getInstance(requestValues.context)?.currentActivity?.get()
val dialog = HabiticaAlertDialog(requestValues.context)
- dialog.setTitle(requestValues.context.getString(R.string.hatched_pet_title, potionName, eggName))
- dialog.setAdditionalContentView(petWrapper)
+ if (currentActivity != null) {
+ petWrapper.backgroundView.setContent {
+ HabiticaTheme {
+ BackgroundScene(Modifier.clip(HabiticaTheme.shapes.large))
+ }
+ }
+ dialog.window?.let {
+ petWrapper.root.setViewTreeSavedStateRegistryOwner(currentActivity)
+ it.decorView.setViewTreeSavedStateRegistryOwner(currentActivity)
+ petWrapper.root.setViewTreeLifecycleOwner(currentActivity)
+ it.decorView.setViewTreeLifecycleOwner(currentActivity)
+ }
+ }
+ dialog.isCelebratory = true
+ dialog.setTitle(
+ requestValues.context.getString(
+ R.string.hatched_pet_title,
+ potionName,
+ eggName
+ )
+ )
+ dialog.setAdditionalContentView(petWrapper.root)
dialog.addButton(R.string.equip, true) { _, _ ->
MainScope().launchCatching {
- inventoryRepository.equip("pet", requestValues.egg.key + "-" + requestValues.potion.key)
+ inventoryRepository.equip(
+ "pet",
+ requestValues.egg.key + "-" + requestValues.potion.key
+ )
}
}
dialog.addButton(R.string.share, false) { hatchingDialog, _ ->
- val message = requestValues.context.getString(R.string.share_hatched, potionName, eggName)
- val petImageSideLength = 140
- val sharedImage = Bitmap.createBitmap(petImageSideLength, petImageSideLength, Bitmap.Config.ARGB_8888)
- val canvas = Canvas(sharedImage)
- petImageView?.drawable?.setBounds(0, 0, petImageSideLength, petImageSideLength)
- petImageView?.drawable?.draw(canvas)
- (requestValues.context as? BaseActivity)?.shareContent("hatchedPet", message, sharedImage)
+ MainScope().launchCatching {
+ SharePetUseCase().callInteractor(
+ SharePetUseCase.RequestValues(
+ petKey,
+ requestValues.context.getString(
+ R.string.share_hatched,
+ potionName,
+ eggName
+ ),
+ requestValues.context
+ )
+ )
+ }
hatchingDialog.dismiss()
}
dialog.setExtraCloseButtonVisibility(View.VISIBLE)
@@ -53,5 +87,6 @@ constructor(
}
}
- class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) : UseCase.RequestValues
+ class RequestValues(val potion: HatchingPotion, val egg: Egg, val context: Context) :
+ UseCase.RequestValues
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt
index 08223e6531..c9efd542c7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/LevelUpUseCase.kt
@@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.interactors
-import android.graphics.Bitmap
import android.view.ViewGroup
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogLevelup10Binding
@@ -63,14 +62,6 @@ constructor(
dialogAvatarView.setAvatar(requestValues.user)
}
- val message = requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel)
- val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true)
- avatarView.setAvatar(requestValues.user)
- var sharedImage: Bitmap? = null
- avatarView.onAvatarImageReady { image ->
- sharedImage = image
- }
-
val alert = HabiticaAlertDialog(requestValues.activity)
alert.setTitle(requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel))
alert.setAdditionalContentView(customView)
@@ -80,7 +71,16 @@ constructor(
}
}
alert.addButton(R.string.share, false) { _, _ ->
- requestValues.activity.shareContent("levelup", message, sharedImage)
+ MainScope().launchCatching {
+ val usecase = ShareAvatarUseCase()
+ usecase.callInteractor(ShareAvatarUseCase.RequestValues(
+ requestValues.activity,
+ requestValues.user,
+ requestValues.activity.getString(R.string.share_levelup, requestValues.newLevel),
+ "levelup"
+ ))
+ }
+
}
alert.isCelebratory = true
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareAvatarUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareAvatarUseCase.kt
new file mode 100644
index 0000000000..a5ce036670
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareAvatarUseCase.kt
@@ -0,0 +1,22 @@
+package com.habitrpg.android.habitica.interactors
+
+import android.graphics.Bitmap
+import androidx.core.graphics.scale
+import com.habitrpg.android.habitica.ui.activities.BaseActivity
+import com.habitrpg.common.habitica.views.AvatarView
+import com.habitrpg.shared.habitica.models.Avatar
+import javax.inject.Inject
+
+class ShareAvatarUseCase @Inject constructor() : UseCase() {
+ override suspend fun run(requestValues: RequestValues) {
+ val avatarView = AvatarView(requestValues.activity, showBackground = true, showMount = true, showPet = true)
+ avatarView.setAvatar(requestValues.avatar)
+ var sharedImage: Bitmap? = null
+ avatarView.onAvatarImageReady { image ->
+ sharedImage = image?.scale(image.width * 3, image.height * 3, false)
+ requestValues.activity.shareContent(requestValues.identifier, requestValues.message, sharedImage)
+ }
+ }
+
+ class RequestValues(val activity: BaseActivity, val avatar: Avatar, val message: String?, val identifier: String): UseCase.RequestValues
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareMountUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareMountUseCase.kt
new file mode 100644
index 0000000000..c95a12280e
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShareMountUseCase.kt
@@ -0,0 +1,75 @@
+package com.habitrpg.android.habitica.interactors
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.core.view.doOnNextLayout
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.habitrpg.android.habitica.HabiticaBaseApplication
+import com.habitrpg.android.habitica.databinding.MountImageviewBinding
+import com.habitrpg.android.habitica.ui.activities.BaseActivity
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.android.habitica.ui.views.BackgroundScene
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
+import com.habitrpg.common.habitica.extensions.dpToPx
+import com.habitrpg.common.habitica.extensions.layoutInflater
+import com.habitrpg.common.habitica.extensions.loadImage
+import kotlinx.coroutines.delay
+
+class ShareMountUseCase: UseCase() {
+ class RequestValues(val mountKey: String, val message: String, val context: Context) :
+ UseCase.RequestValues
+
+ override suspend fun run(requestValues: RequestValues) {
+ val mountWrapper = MountImageviewBinding.inflate(requestValues.context.layoutInflater)
+ mountWrapper.root.visibility = View.INVISIBLE
+ val width = if (mountWrapper.root.width > 0) mountWrapper.root.width else 300.dpToPx(requestValues.context)
+ val height = 124.dpToPx(requestValues.context)
+ mountWrapper.root.layout(0, 0, width, height)
+ mountWrapper.mountImageview.setMount(requestValues.mountKey)
+ val currentActivity =
+ HabiticaBaseApplication.getInstance(requestValues.context)?.currentActivity?.get()
+ // Add the view to the decorView so that it can be layouted
+ val containerView = (currentActivity?.window?.decorView as? ViewGroup)
+ containerView?.addView(mountWrapper.root)
+ if (currentActivity != null) {
+ mountWrapper.backgroundView.setContent {
+ HabiticaTheme {
+ BackgroundScene(Modifier.clip(HabiticaTheme.shapes.large))
+ }
+ }
+ mountWrapper.root.setViewTreeSavedStateRegistryOwner(currentActivity)
+ mountWrapper.root.setViewTreeLifecycleOwner(currentActivity)
+ }
+ mountWrapper.backgroundView.layout(0, 0, width, height)
+ mountWrapper.mountImageview.layout(0, 0, width, height)
+ val sharedImage = Bitmap.createBitmap(
+ width,
+ height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(sharedImage)
+ var attempts = 0
+ while (!mountWrapper.mountImageview.hasLoadedImages && attempts < 200) {
+ delay(100)
+ attempts++
+ }
+ // Draw it to the canvas once it's layouted
+ mountWrapper.root.doOnNextLayout {
+ mountWrapper.root.draw(canvas)
+ ((requestValues.context as? BaseActivity) ?: HabiticaBaseApplication.getInstance(
+ requestValues.context
+ )?.currentActivity?.get())?.shareContent("pet", requestValues.message, sharedImage)
+ containerView?.removeView(mountWrapper.root)
+ }
+ // trigger layout
+ val m = FrameLayout.LayoutParams(width, height)
+ mountWrapper.root.layoutParams = m
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/SharePetUseCase.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/SharePetUseCase.kt
new file mode 100644
index 0000000000..dc88669a1c
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/SharePetUseCase.kt
@@ -0,0 +1,82 @@
+package com.habitrpg.android.habitica.interactors
+
+import android.app.Activity
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.view.PixelCopy
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.annotation.RequiresApi
+import androidx.appcompat.widget.Toolbar
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.platform.findViewTreeCompositionContext
+import androidx.core.view.doOnNextLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.habitrpg.android.habitica.HabiticaBaseApplication
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.PetImageviewBinding
+import com.habitrpg.android.habitica.models.inventory.Food
+import com.habitrpg.android.habitica.models.inventory.Pet
+import com.habitrpg.android.habitica.ui.activities.BaseActivity
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.android.habitica.ui.views.BackgroundScene
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
+import com.habitrpg.common.habitica.extensions.dpToPx
+import com.habitrpg.common.habitica.extensions.layoutInflater
+import com.habitrpg.common.habitica.extensions.loadImage
+import kotlinx.coroutines.delay
+
+class SharePetUseCase: UseCase() {
+ class RequestValues(val petKey: String, val message: String, val context: Context) :
+ UseCase.RequestValues
+
+ override suspend fun run(requestValues: RequestValues) {
+ val petWrapper = PetImageviewBinding.inflate(requestValues.context.layoutInflater)
+ petWrapper.petImageview.loadImage("stable_Pet-" + requestValues.petKey)
+ petWrapper.root.visibility = View.INVISIBLE
+ val currentActivity =
+ HabiticaBaseApplication.getInstance(requestValues.context)?.currentActivity?.get()
+ val containerView = (currentActivity?.window?.decorView as? ViewGroup)
+ containerView?.addView(petWrapper.root)
+ if (currentActivity != null) {
+ petWrapper.backgroundView.setContent {
+ HabiticaTheme {
+ BackgroundScene(Modifier.clip(HabiticaTheme.shapes.large))
+ }
+ }
+ petWrapper.backgroundView.setParentCompositionContext(currentActivity.toolbar?.findViewTreeCompositionContext())
+ petWrapper.root.setViewTreeSavedStateRegistryOwner(currentActivity)
+ petWrapper.root.setViewTreeLifecycleOwner(currentActivity)
+ }
+ val width = if (petWrapper.root.width > 0) petWrapper.root.width else 300.dpToPx(requestValues.context)
+ val height = 124.dpToPx(requestValues.context)
+ val sharedImage = Bitmap.createBitmap(
+ width,
+ height,
+ Bitmap.Config.ARGB_8888
+ )
+ val canvas = Canvas(sharedImage)
+ var attempts = 0
+ while (petWrapper.petImageview.bitmap == null && attempts < 200) {
+ delay(100)
+ attempts++
+ }
+ petWrapper.root.doOnNextLayout {
+ petWrapper.root.draw(canvas)
+ ((requestValues.context as? BaseActivity) ?: HabiticaBaseApplication.getInstance(
+ requestValues.context
+ )?.currentActivity?.get())?.shareContent("pet", requestValues.message, sharedImage)
+ containerView?.removeView(petWrapper.root)
+ }
+ val m = FrameLayout.LayoutParams(width, height)
+ petWrapper.root.layoutParams = m
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt
index 0482f9ea21..acf94c6fe9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/Tag.kt
@@ -10,6 +10,7 @@ open class Tag : RealmObject(), BaseObject {
var userId: String? = null
var name: String = ""
+ var group: String? = null
internal var challenge: Boolean = false
override fun equals(other: Any?): Boolean {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Animal.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Animal.kt
index 11de86c154..1dc31f5b65 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Animal.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Animal.kt
@@ -1,7 +1,7 @@
package com.habitrpg.android.habitica.models.inventory
interface Animal {
- var key: String?
+ var key: String
var text: String?
var type: String?
var animal: String
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt
index 8674dbe061..dac278cb1a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Customization.kt
@@ -46,33 +46,26 @@ open class Customization : RealmObject(), BaseObject {
return !(availableUntil != null && !availableUntil!!.after(today))
}
- fun getIconName(userSize: String?, hairColor: String?): String {
- return if (type == "background") {
- "icon_background_$identifier"
- } else {
- "icon_" + getImageName(userSize, hairColor)
- }
+ fun getIconName(userSize: String?, hairColor: String?): String? {
+ return "icon_" + (getImageName(userSize, hairColor) ?: return null)
}
- fun getImageName(userSize: String?, hairColor: String?): String {
- when (type) {
+ fun getImageName(userSize: String?, hairColor: String?): String? {
+ if (identifier?.isNotBlank() != true || identifier == "none" || identifier == "0") return null
+ return when (type) {
"skin" -> return "skin_$identifier"
"shirt" -> return userSize + "_shirt_" + identifier
"hair" -> {
- return if (identifier == "0") {
- "head_0"
- } else {
when (this.category) {
"color" -> "hair_bangs_1_$identifier"
"flower" -> "hair_flower_$identifier"
else -> "hair_" + this.category + "_" + identifier + "_" + hairColor
}
- }
}
"background" -> return "background_$identifier"
"chair" -> return "chair_$identifier"
+ else -> null
}
- return ""
}
fun isUsable(purchased: Boolean): Boolean {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Mount.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Mount.kt
index 1783626987..d0a7247a74 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Mount.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Mount.kt
@@ -6,21 +6,17 @@ import io.realm.annotations.PrimaryKey
open class Mount : RealmObject(), Animal {
@PrimaryKey
- override var key: String? = null
+ override var key: String = ""
override var animal: String = ""
get() {
- return if (field.isBlank()) {
- key?.split("-")?.toTypedArray()?.get(0) ?: ""
- } else {
- field
+ return field.ifBlank {
+ key.split("-").toTypedArray()[0]
}
}
override var color: String = ""
get() {
- return if (field.isBlank()) {
- key?.split("-")?.toTypedArray()?.get(1) ?: ""
- } else {
- field
+ return field.ifBlank {
+ key.split("-").toTypedArray()[1]
}
}
override var text: String? = null
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Pet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Pet.kt
index ef90863c4d..275c01075d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Pet.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/Pet.kt
@@ -6,21 +6,17 @@ import io.realm.annotations.PrimaryKey
open class Pet : RealmObject(), Animal {
@PrimaryKey
- override var key: String? = null
+ override var key: String = ""
override var animal: String = ""
get() {
- return if (field.isBlank()) {
- key?.split("-")?.toTypedArray()?.get(0) ?: ""
- } else {
- field
+ return field.ifBlank {
+ key.split("-").toTypedArray()[0]
}
}
override var color: String = ""
get() {
- return if (field.isBlank()) {
- key?.split("-")?.toTypedArray()?.get(1) ?: ""
- } else {
- field
+ return field.ifBlank {
+ key.split("-").toTypedArray()[1]
}
}
override var text: String? = null
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/QuestBossRage.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/QuestBossRage.kt
index f6788b2211..2396c05e55 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/QuestBossRage.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/inventory/QuestBossRage.kt
@@ -14,7 +14,6 @@ open class QuestBossRage : RealmObject(), BaseObject {
var description: String? = null
var value: Double = 0.toDouble()
- var tavern: String? = null
var stables: String? = null
var market: String? = null
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/FallExtraGemsHabiticaPromotion.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/FallExtraGemsHabiticaPromotion.kt
index 0fb1d45c79..afefb281fd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/FallExtraGemsHabiticaPromotion.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/FallExtraGemsHabiticaPromotion.kt
@@ -12,12 +12,13 @@ import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding
import com.habitrpg.android.habitica.databinding.PurchaseGemViewBinding
import com.habitrpg.android.habitica.extensions.DateUtils
-import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+import java.util.TimeZone
class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : HabiticaPromotion() {
override val identifier: String
@@ -50,7 +51,12 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
view.setBackgroundColor(backgroundColor(context))
view.setTitleImage(ContextCompat.getDrawable(context, R.drawable.fall_promo_title))
view.setTitleText(null)
- view.setSubtitleImage(ContextCompat.getDrawable(context, R.drawable.fall_promo_menu_description))
+ view.setSubtitleImage(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_menu_description
+ )
+ )
view.setSubtitleText(null)
view.setDecoration(
@@ -58,7 +64,8 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
ContextCompat.getDrawable(context, R.drawable.fall_promo_menu_right)
)
- view.binding.button.backgroundTintList = ContextCompat.getColorStateList(context, R.color.gray_1)
+ view.binding.button.backgroundTintList =
+ ContextCompat.getColorStateList(context, R.color.gray_1)
view.binding.button.setText(R.string.learn_more)
view.binding.button.setTextColor(ContextCompat.getColor(context, R.color.white))
view.binding.button.setOnClickListener {
@@ -74,9 +81,24 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
val context = binding.root.context
binding.promoBanner.visibility = View.VISIBLE
binding.promoBanner.background = promoBackgroundDrawable(context)
- binding.promoBannerLeftImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.fall_promo_banner_left))
- binding.promoBannerRightImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.fall_promo_banner_right))
- binding.promoBannerTitleImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.fall_promo_title))
+ binding.promoBannerLeftImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_banner_left
+ )
+ )
+ binding.promoBannerRightImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_banner_right
+ )
+ )
+ binding.promoBannerTitleImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_title
+ )
+ )
val formatter = SimpleDateFormat("MMM d", Locale.getDefault())
binding.promoBannerDurationView.text = context.getString(
R.string.x_to_y,
@@ -104,18 +126,22 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
binding.gemAmount.text = "5"
binding.gemImage.setImageResource(R.drawable.fall_gems_4)
}
+
21 -> {
binding.gemAmount.text = "30"
binding.gemImage.setImageResource(R.drawable.fall_gems_21)
}
+
42 -> {
binding.gemAmount.text = "60"
binding.gemImage.setImageResource(R.drawable.fall_gems_42)
}
+
84 -> {
binding.gemAmount.text = "125"
binding.gemImage.setImageResource(R.drawable.fall_gems_84)
}
+
else -> regularAmount.toString()
}
}
@@ -123,9 +149,24 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
override fun configureInfoFragment(fragment: PromoInfoFragment) {
val context = fragment.context ?: return
fragment.binding?.promoBanner?.background = promoBackgroundDrawable(context)
- fragment.binding?.promoBannerLeftImage?.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.fall_promo_info_left))
- fragment.binding?.promoBannerRightImage?.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.fall_promo_info_right))
- fragment.binding?.promoBannerTitleImage?.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.fall_promo_title))
+ fragment.binding?.promoBannerLeftImage?.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_info_left
+ )
+ )
+ fragment.binding?.promoBannerRightImage?.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_info_right
+ )
+ )
+ fragment.binding?.promoBannerTitleImage?.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.fall_promo_title
+ )
+ )
fragment.binding?.promoBannerSubtitleView?.setText(R.string.limited_event)
fragment.binding?.promoBannerDurationView?.setTextColor(Color.parseColor("#FEE2B6"))
val formatter = SimpleDateFormat("MMM d", Locale.getDefault())
@@ -134,7 +175,12 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
formatter.format(startDate),
formatter.format(endDate)
)
- fragment.binding?.promoBannerDurationView?.setTextColor(ContextCompat.getColor(context, R.color.white))
+ fragment.binding?.promoBannerDurationView?.setTextColor(
+ ContextCompat.getColor(
+ context,
+ R.color.white
+ )
+ )
fragment.binding?.promptText?.setText(R.string.fall_promo_info_prompt)
fragment.binding?.promptText?.setTextColor(Color.parseColor("#F78E2F"))
fragment.binding?.promptButton?.background = buttonDrawable(context)
@@ -142,8 +188,21 @@ class FallExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
fragment.binding?.promptButton?.setTextColor(ContextCompat.getColor(context, R.color.white))
fragment.binding?.promptButton?.setOnClickListener { MainNavigationController.navigate(R.id.gemPurchaseActivity) }
- fragment.binding?.instructionDescriptionView?.text = context.getString(R.string.fall_promo_info_instructions, formatter.format(startDate), formatter.format(endDate))
- val limitationsFormatter = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG, SimpleDateFormat.LONG)
- fragment.binding?.limitationsDescriptionView?.text = context.getString(R.string.gems_promo_info_limitations, limitationsFormatter.format(startDate), limitationsFormatter.format(endDate))
+ fragment.binding?.instructionDescriptionView?.text = context.getString(
+ R.string.fall_promo_info_instructions,
+ formatter.format(startDate),
+ formatter.format(endDate)
+ )
+ val limitationsFormatter =
+ SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG, SimpleDateFormat.LONG)
+ val utcTimeFormatter = SimpleDateFormat.getTimeInstance(SimpleDateFormat.LONG)
+ utcTimeFormatter.timeZone = TimeZone.getTimeZone("UTC")
+ fragment.binding?.limitationsDescriptionView?.text = context.getString(
+ R.string.gems_promo_info_limitations_fixed,
+ limitationsFormatter.format(startDate),
+ utcTimeFormatter.format(startDate),
+ limitationsFormatter.format(endDate),
+ utcTimeFormatter.format(endDate)
+ )
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/GiftOneGetOneHabiticaPromotion.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/GiftOneGetOneHabiticaPromotion.kt
index 44028e1a00..c1513dbd37 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/GiftOneGetOneHabiticaPromotion.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/GiftOneGetOneHabiticaPromotion.kt
@@ -10,14 +10,15 @@ import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding
import com.habitrpg.android.habitica.databinding.PurchaseGemViewBinding
import com.habitrpg.android.habitica.extensions.DateUtils
-import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.android.habitica.ui.fragments.purchases.SubscriptionFragment
import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+import java.util.TimeZone
class GiftOneGetOneHabiticaPromotion(startDate: Date?, endDate: Date?) : HabiticaPromotion() {
override val identifier: String
@@ -56,7 +57,8 @@ class GiftOneGetOneHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
ContextCompat.getDrawable(context, R.drawable.g1g1_promo_menu_right)
)
- view.binding.button.backgroundTintList = ContextCompat.getColorStateList(context, R.color.content_background)
+ view.binding.button.backgroundTintList =
+ ContextCompat.getColorStateList(context, R.color.content_background)
view.binding.button.setText(R.string.learn_more)
if (context.isUsingNightModeResources()) {
view.binding.button.setTextColor(ContextCompat.getColor(context, R.color.teal_100))
@@ -76,26 +78,48 @@ class GiftOneGetOneHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
val context = binding.root.context
binding.promoBanner.visibility = View.VISIBLE
binding.promoBanner.background = promoBackgroundDrawable(context)
- binding.promoBannerLeftImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.g1g1_promo_left_small))
- binding.promoBannerRightImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.g1g1_promo_right_small))
+ binding.promoBannerLeftImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.g1g1_promo_left_small
+ )
+ )
+ binding.promoBannerRightImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.g1g1_promo_right_small
+ )
+ )
binding.promoBannerTitleImage.visibility = View.GONE
binding.promoBannerDurationView.visibility = View.GONE
binding.promoBannerTitleText.visibility = View.VISIBLE
val formatter = SimpleDateFormat("MMM d", Locale.getDefault())
- binding.promoBannerTitleText.text = context.getString(R.string.gift_one_get_one_purchase_banner, formatter.format(endDate))
+ binding.promoBannerTitleText.text =
+ context.getString(R.string.gift_one_get_one_purchase_banner, formatter.format(endDate))
}
override fun configurePurchaseBanner(binding: FragmentSubscriptionBinding) {
val context = binding.root.context
binding.promoBanner.visibility = View.VISIBLE
binding.promoBanner.background = promoBackgroundDrawable(context)
- binding.promoBannerLeftImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.g1g1_promo_left_small))
- binding.promoBannerRightImage.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.g1g1_promo_right_small))
+ binding.promoBannerLeftImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.g1g1_promo_left_small
+ )
+ )
+ binding.promoBannerRightImage.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.g1g1_promo_right_small
+ )
+ )
binding.promoBannerTitleImage.visibility = View.GONE
binding.promoBannerDurationView.visibility = View.GONE
binding.promoBannerTitleText.visibility = View.VISIBLE
val formatter = SimpleDateFormat("MMM d", Locale.getDefault())
- binding.promoBannerTitleText.text = context.getString(R.string.gift_one_get_one_purchase_banner, formatter.format(endDate))
+ binding.promoBannerTitleText.text =
+ context.getString(R.string.gift_one_get_one_purchase_banner, formatter.format(endDate))
}
override fun configureGemView(binding: PurchaseGemViewBinding, regularAmount: Int) {
@@ -104,8 +128,18 @@ class GiftOneGetOneHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
override fun configureInfoFragment(fragment: PromoInfoFragment) {
val context = fragment.context ?: return
fragment.binding?.promoBanner?.background = promoBackgroundDrawable(context)
- fragment.binding?.promoBannerLeftImage?.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.g1g1_promo_left))
- fragment.binding?.promoBannerRightImage?.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.g1g1_promo_right))
+ fragment.binding?.promoBannerLeftImage?.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.g1g1_promo_left
+ )
+ )
+ fragment.binding?.promoBannerRightImage?.setImageDrawable(
+ ContextCompat.getDrawable(
+ context,
+ R.drawable.g1g1_promo_right
+ )
+ )
fragment.binding?.promoBannerTitleImage?.visibility = View.GONE
fragment.binding?.promoBannerTitleText?.visibility = View.VISIBLE
fragment.binding?.promoBannerTitleText?.text = context.getString(R.string.gift_one_get_one)
@@ -116,18 +150,44 @@ class GiftOneGetOneHabiticaPromotion(startDate: Date?, endDate: Date?) : Habitic
formatter.format(startDate),
formatter.format(endDate)
)
- fragment.binding?.promoBannerDurationView?.setTextColor(ContextCompat.getColor(context, R.color.white))
+ fragment.binding?.promoBannerDurationView?.setTextColor(
+ ContextCompat.getColor(
+ context,
+ R.color.white
+ )
+ )
fragment.binding?.promptText?.setText(R.string.g1g1_promo_info_prompt)
- fragment.binding?.promptText?.setTextColor(ContextCompat.getColor(context, R.color.text_teal))
- fragment.binding?.promptButton?.background = ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg_brand_400)
+ fragment.binding?.promptText?.setTextColor(
+ ContextCompat.getColor(
+ context,
+ R.color.text_teal
+ )
+ )
+ fragment.binding?.promptButton?.background =
+ ContextCompat.getDrawable(context, R.drawable.layout_rounded_bg_brand_400)
fragment.binding?.promptButton?.setText(R.string.gift_a_subscription)
fragment.binding?.promptButton?.setTextColor(ContextCompat.getColor(context, R.color.white))
fragment.binding?.promptButton?.setOnClickListener {
- fragment.context?.let { context -> SubscriptionFragment.showGiftSubscriptionDialog(context, true) }
+ fragment.context?.let { context ->
+ SubscriptionFragment.showGiftSubscriptionDialog(
+ context,
+ true
+ )
+ }
}
- fragment.binding?.instructionDescriptionView?.text = context.getString(R.string.g1g1_promo_info_instructions)
- val limitationsFormatter = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG, SimpleDateFormat.LONG)
- fragment.binding?.limitationsDescriptionView?.text = context.getString(R.string.g1g1_promo_info_limitations, limitationsFormatter.format(startDate), limitationsFormatter.format(endDate))
+ fragment.binding?.instructionDescriptionView?.text =
+ context.getString(R.string.g1g1_promo_info_instructions)
+ val limitationsFormatter =
+ SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.LONG, SimpleDateFormat.LONG)
+ val utcTimeFormatter = SimpleDateFormat.getTimeInstance(SimpleDateFormat.LONG)
+ utcTimeFormatter.timeZone = TimeZone.getTimeZone("UTC")
+ fragment.binding?.limitationsDescriptionView?.text = context.getString(
+ R.string.g1g1_promo_info_limitations_fixed,
+ limitationsFormatter.format(startDate),
+ utcTimeFormatter.format(startDate),
+ limitationsFormatter.format(endDate),
+ utcTimeFormatter.format(startDate)
+ )
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/SpookyExtraGemsHabiticaPromotion.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/SpookyExtraGemsHabiticaPromotion.kt
index 4ebcea8fbc..0c44254350 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/SpookyExtraGemsHabiticaPromotion.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/SpookyExtraGemsHabiticaPromotion.kt
@@ -12,12 +12,13 @@ import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding
import com.habitrpg.android.habitica.databinding.PurchaseGemViewBinding
import com.habitrpg.android.habitica.extensions.DateUtils
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
+import java.util.TimeZone
class SpookyExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : HabiticaPromotion() {
override val identifier: String
@@ -144,6 +145,14 @@ class SpookyExtraGemsHabiticaPromotion(startDate: Date?, endDate: Date?) : Habit
fragment.binding?.instructionDescriptionView?.text = context.getString(R.string.spooky_promo_info_instructions, formatter.format(startDate), formatter.format(endDate))
val limitationsFormatter = SimpleDateFormat.getDateTimeInstance()
- fragment.binding?.limitationsDescriptionView?.text = context.getString(R.string.gems_promo_info_limitations, limitationsFormatter.format(startDate), limitationsFormatter.format(endDate))
+ val utcTimeFormatter = SimpleDateFormat.getTimeInstance(SimpleDateFormat.LONG)
+ utcTimeFormatter.timeZone = TimeZone.getTimeZone("UTC")
+ fragment.binding?.limitationsDescriptionView?.text = context.getString(
+ R.string.gems_promo_info_limitations_fixed,
+ limitationsFormatter.format(startDate),
+ utcTimeFormatter.format(startDate),
+ limitationsFormatter.format(endDate),
+ utcTimeFormatter.format(endDate)
+ )
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/Survey2021Promotion.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/Survey2021Promotion.kt
index 858c99b3e9..c1cd90f5c4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/Survey2021Promotion.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/promotions/Survey2021Promotion.kt
@@ -8,7 +8,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding
import com.habitrpg.android.habitica.databinding.PurchaseGemViewBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
import com.habitrpg.android.habitica.ui.fragments.PromoWebFragmentArgs
import com.habitrpg.android.habitica.ui.views.promo.PromoMenuView
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.kt
index 23a5b8cc44..996bd5d73b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/shops/ShopItem.kt
@@ -74,6 +74,7 @@ open class ShopItem : RealmObject(), BaseObject {
fun canAfford(user: User?, quantity: Int): Boolean = when (currency) {
"gold" -> (value * quantity) <= (user?.stats?.gp ?: 0.0)
"gems" -> (value * quantity) <= (user?.gemCount ?: 0)
+ "hourglasses" -> (value * quantity) <= (user?.hourglassCount ?: 0)
else -> true
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt
index 8268b89f51..d8d6fd1289 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.kt
@@ -9,7 +9,6 @@ import com.habitrpg.android.habitica.data.implementation.ApiClientImpl.Companion
import com.habitrpg.android.habitica.helpers.MainNotificationsManager
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.common.habitica.api.HostConfig
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.KeyHelper
import dagger.Module
import dagger.Provides
@@ -50,14 +49,12 @@ open class ApiModule {
fun providesApiHelper(
gsonConverter: GsonConverterFactory,
hostConfig: HostConfig,
- analyticsManager: AnalyticsManager,
notificationsManager: NotificationsManager,
@ApplicationContext context: Context
): ApiClient {
val apiClient = ApiClientImpl(
gsonConverter,
hostConfig,
- analyticsManager,
notificationsManager,
context
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt
index 45fcb8d750..61321766c2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/AppModule.kt
@@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.BuildConfig
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.ReviewManager
import com.habitrpg.android.habitica.helpers.SoundFileLoader
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.common.habitica.helpers.KeyHelper
@@ -100,4 +101,9 @@ class AppModule {
fun providesRemoteConfigManager(contentRepository: ContentRepository?): AppConfigManager {
return AppConfigManager(contentRepository)
}
+
+ @Provides
+ fun providesReviewManager(@ApplicationContext context: Context, configManager: AppConfigManager): ReviewManager {
+ return ReviewManager(context, configManager)
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/DeveloperModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/DeveloperModule.kt
index 91b2e83394..9557b6514a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/DeveloperModule.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/DeveloperModule.kt
@@ -1,22 +1,12 @@
package com.habitrpg.android.habitica.modules
-import android.content.Context
-import com.habitrpg.android.habitica.proxy.implementation.EmptyAnalyticsManager
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import dagger.Module
-import dagger.Provides
import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
-import javax.inject.Singleton
// provide proxy class for libraries(to avoid 65k limit)
@InstallIn(SingletonComponent::class)
@Module
open class DeveloperModule {
- @Provides
- @Singleton
- open fun provideAnalyticsManager(@ApplicationContext context: Context): AnalyticsManager {
- return EmptyAnalyticsManager()
- }
+
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt
index 6c6bffc7b6..b27cd90d74 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/UserRepositoryModule.kt
@@ -43,7 +43,6 @@ import com.habitrpg.android.habitica.data.local.implementation.RealmUserLocalRep
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -71,14 +70,12 @@ class UserRepositoryModule {
apiClient: ApiClient,
authenticationHandler: AuthenticationHandler,
appConfigManager: AppConfigManager,
- analyticsManager: AnalyticsManager
): TaskRepository {
return TaskRepositoryImpl(
localRepository,
apiClient,
authenticationHandler,
- appConfigManager,
- analyticsManager
+ appConfigManager
)
}
@@ -122,7 +119,6 @@ class UserRepositoryModule {
authenticationHandler: AuthenticationHandler,
taskRepository: TaskRepository,
appConfigManager: AppConfigManager,
- analyticsManager: AnalyticsManager
): UserRepository {
return UserRepositoryImpl(
localRepository,
@@ -130,7 +126,6 @@ class UserRepositoryModule {
authenticationHandler,
taskRepository,
appConfigManager,
- analyticsManager
)
}
@@ -211,10 +206,9 @@ class UserRepositoryModule {
@Singleton
fun providesPurchaseHandler(
@ApplicationContext context: Context,
- analyticsManager: AnalyticsManager,
apiClient: ApiClient,
userViewModel: MainUserViewModel
): PurchaseHandler {
- return PurchaseHandler(context, analyticsManager, apiClient, userViewModel)
+ return PurchaseHandler(context, apiClient, userViewModel)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt
deleted file mode 100644
index 16a7b19423..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/proxy/implementation/EmptyAnalyticsManager.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.habitrpg.android.habitica.proxy.implementation
-
-import android.os.Bundle
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
-
-class EmptyAnalyticsManager : AnalyticsManager {
-
- override fun logException(t: Throwable) {
- // pass
- }
-
- override fun setUserIdentifier(identifier: String) {
- // pass
- }
-
- override fun setUserProperty(identifier: String, value: String) {
- // pass
- }
-
- override fun logError(msg: String) {
- // pass
- }
-
- override fun logEvent(eventName: String, data: Bundle) {
- // pass
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/TutorialView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/TutorialView.kt
index 2b93662888..8d1079dfb9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/TutorialView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/TutorialView.kt
@@ -6,7 +6,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.OverlayTutorialBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.common.habitica.extensions.layoutInflater
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt
index 6f1971f9c9..371b8b51b2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/AdventureGuideActivity.kt
@@ -12,7 +12,7 @@ import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityAdventureGuideBinding
import com.habitrpg.android.habitica.databinding.AdventureGuideItemBinding
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.extensions.fromHtml
@@ -62,7 +62,7 @@ class AdventureGuideActivity : BaseActivity() {
val descriptionText = getString(R.string.adventure_guide_description_new)
binding.descriptionView.setText(descriptionText.fromHtml(), TextView.BufferType.SPANNABLE)
- AmplitudeManager.sendNavigationEvent("adventure guide screen")
+ Analytics.sendNavigationEvent("adventure guide screen")
userViewModel.user.observe(this) {
if (it != null) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt
index 49d4810cad..d6abcc8bbb 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ArmoireActivity.kt
@@ -2,31 +2,43 @@ package com.habitrpg.android.habitica.ui.activities
import android.annotation.SuppressLint
import android.os.Bundle
-import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.widget.FrameLayout
import android.widget.RelativeLayout
+import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.ActivityArmoireBinding
+import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.android.habitica.helpers.ReviewManager
+import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment
+import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment.Companion.EVENT_ARMOIRE_OPENED
+import com.habitrpg.android.habitica.ui.fragments.purchases.SubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
+import com.habitrpg.android.habitica.ui.views.progress.HabiticaCircularProgressView
import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.Animations
import com.habitrpg.common.habitica.helpers.ExceptionHandler
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.helpers.launchCatching
import com.plattysoft.leonids.ParticleSystem
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import java.util.Locale
@@ -49,6 +61,9 @@ class ArmoireActivity : BaseActivity() {
@Inject
lateinit var userViewModel: MainUserViewModel
+ @Inject
+ lateinit var reviewManager: ReviewManager
+
override fun getLayoutResId(): Int = R.layout.activity_armoire
override fun getContentView(layoutResId: Int?): View {
@@ -76,28 +91,16 @@ class ArmoireActivity : BaseActivity() {
}
}
+ binding.progressView.setContent {
+ HabiticaCircularProgressView(indicatorSize = 60.dp)
+ }
+
if (appConfigManager.enableArmoireAds()) {
val handler = AdHandler(this, AdType.ARMOIRE) {
if (!it) {
return@AdHandler
}
- Log.d("AdHandler", "Giving Armoire")
- val user = userViewModel.user.value ?: return@AdHandler
- val currentGold = user.stats?.gp ?: return@AdHandler
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- userRepository.updateUser("stats.gp", currentGold + 100)
- val buyResponse = inventoryRepository.buyItem(user, "armoire", 100.0, 1) ?: return@launch
- configure(
- buyResponse.armoire["type"] ?: "",
- buyResponse.armoire["dropKey"] ?: "",
- buyResponse.armoire["dropText"] ?: "",
- buyResponse.armoire["value"] ?: ""
- )
- binding.adButton.state = AdButton.State.UNAVAILABLE
- binding.adButton.visibility = View.INVISIBLE
- hasAnimatedChanges = false
- gold = null
- }
+ giveUserArmoire()
}
handler.prepare {
if (it && binding.adButton.state == AdButton.State.LOADING) {
@@ -115,6 +118,41 @@ class ArmoireActivity : BaseActivity() {
binding.adButton.visibility = View.GONE
}
+ if (appConfigManager.enableArmoireSubs()) {
+ userViewModel.user.observe(this) {
+ if (it?.isSubscribed == true && binding.openArmoireSubscriberWrapper.visibility != View.INVISIBLE) {
+ binding.openArmoireSubscriberWrapper.visibility = View.VISIBLE
+ binding.unsubbedWrapper.visibility = View.GONE
+ binding.dropRateButton.visibility = View.VISIBLE
+ } else if (it?.isSubscribed == false) {
+ binding.openArmoireSubscriberWrapper.visibility = View.GONE
+ binding.unsubbedWrapper.visibility = View.VISIBLE
+ binding.dropRateButton.visibility = View.GONE
+ }
+ }
+ } else {
+ binding.openArmoireSubscriberWrapper.visibility = View.GONE
+ binding.unsubbedWrapper.visibility = View.GONE
+ binding.dropRateButton.visibility = View.VISIBLE
+ }
+
+ binding.openArmoireSubscriberButton.setOnClickListener {
+ Analytics.sendEvent("Free armoire perk", EventCategory.BEHAVIOUR, HitType.EVENT)
+ giveUserArmoire()
+ lifecycleScope.launchCatching {
+ delay(400)
+ binding.openArmoireSubscriberWrapper.startAnimation(Animations.fadeOutAnimation())
+ }
+ }
+
+ binding.subscribeModalButton.setOnClickListener {
+ Analytics.sendEvent("View armoire sub CTA", EventCategory.BEHAVIOUR, HitType.EVENT)
+ val subscriptionBottomSheet = EventOutcomeSubscriptionBottomSheetFragment().apply {
+ eventType = EVENT_ARMOIRE_OPENED
+ }
+ subscriptionBottomSheet.show(supportFragmentManager, EventOutcomeSubscriptionBottomSheetFragment.TAG)
+ }
+
binding.closeButton.setOnClickListener {
finish()
}
@@ -127,22 +165,89 @@ class ArmoireActivity : BaseActivity() {
binding.dropRateButton.setOnClickListener {
showDropRateDialog()
}
+ binding.dropRateButtonUnsubbed.setOnClickListener {
+ showDropRateDialog()
+ }
intent.extras?.let {
val args = ArmoireActivityArgs.fromBundle(it)
equipmentKey = args.key
configure(args.type, args.key, args.text, args.value)
+
+ if (args.type == "gear") {
+ userViewModel.user.observeOnce(this) { user ->
+ user?.loginIncentives?.let { totalCheckins ->
+ reviewManager.requestReview(this@ArmoireActivity, totalCheckins)
+ }
+ }
+ }
+ }
+ }
+
+ private fun giveUserArmoire(): Boolean {
+ binding.iconWrapper.post {
+ binding.iconView.bitmap = null
+ Animations.circularHide(binding.iconWrapper)
+ }
+ binding.progressView.animate().apply {
+ alpha(1f)
+ startDelay = 0
+ start()
+ }
+ binding.titleView.animate().apply {
+ alpha(0f)
+ duration = 300
+ startDelay = 0
+ start()
+ }
+ binding.subtitleView.animate().apply {
+ alpha(0f)
+ duration = 300
+ startDelay = 0
+ start()
+ }
+
+ binding.goldView.animate().apply {
+ alpha(0f)
+ start()
+ }
+
+ val user = userViewModel.user.value ?: return true
+ val currentGold = user.stats?.gp ?: return true
+ if (binding.adButton.visibility == View.VISIBLE) {
+ binding.adButton.state = AdButton.State.UNAVAILABLE
+ binding.adButton.visibility = View.INVISIBLE
+ } else if (binding.openArmoireSubscriberWrapper.visibility == View.VISIBLE) {
+ binding.openArmoireSubscriberWrapper.visibility = View.INVISIBLE
+ }
+ lifecycleScope.launch(ExceptionHandler.coroutine()) {
+ userRepository.updateUser("stats.gp", currentGold + 100)
+ val buyResponse =
+ inventoryRepository.buyItem(user, "armoire", 0.0, 1) ?: return@launch
+ configure(
+ buyResponse.armoire["type"] ?: "",
+ buyResponse.armoire["dropKey"] ?: "",
+ buyResponse.armoire["dropText"] ?: "",
+ buyResponse.armoire["value"] ?: ""
+ )
+ hasAnimatedChanges = false
+ gold = null
+ startAnimation(false)
}
+ return false
}
override fun onResume() {
super.onResume()
- startAnimation()
+ lifecycleScope.launchCatching {
+ delay(500L)
+ startAnimation(true)
+ }
}
- private fun startAnimation() {
+ private fun startAnimation(decreaseGold: Boolean) {
val gold = gold?.toInt()
if (hasAnimatedChanges) return
- if (gold != null) {
+ if (gold != null && decreaseGold) {
/**
* We are adding 100 as the gold is already "deducted" before the animation starts,
* and animating to show the current user's gold amount.
@@ -151,6 +256,12 @@ class ArmoireActivity : BaseActivity() {
binding.goldView.value = (gold).toDouble()
}
+ binding.progressView.animate().apply {
+ alpha(0f)
+ startDelay = 0
+ start()
+ }
+
val container = binding.confettiAnchor
container.postDelayed(
{
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
index 9147b28e7b..042a1d0ef4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BaseActivity.kt
@@ -1,12 +1,17 @@
package com.habitrpg.android.habitica.ui.activities
+import android.R.attr.bitmap
+import android.R.attr.name
+import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
+import android.os.Build
import android.os.Bundle
+import android.os.Environment
import android.provider.MediaStore
import android.view.LayoutInflater
import android.view.Menu
@@ -16,7 +21,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
-import androidx.core.os.bundleOf
+import androidx.core.net.toUri
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.HabiticaApplication
@@ -24,20 +29,26 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.forceLocale
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.interactors.ShowNotificationInteractor
import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.LanguageHelper
import com.habitrpg.common.habitica.helpers.launchCatching
import kotlinx.coroutines.launch
+import java.io.File
+import java.io.FileOutputStream
+import java.io.OutputStream
import java.util.Date
import javax.inject.Inject
+
abstract class BaseActivity : AppCompatActivity() {
@Inject
lateinit var notificationsManager: NotificationsManager
@@ -45,9 +56,6 @@ abstract class BaseActivity : AppCompatActivity() {
@Inject
lateinit var userRepository: UserRepository
- @Inject
- internal lateinit var analyticsManager: AnalyticsManager
-
private var currentTheme: String? = null
private var isNightMode: Boolean = false
internal var forcedTheme: String? = null
@@ -215,7 +223,7 @@ abstract class BaseActivity : AppCompatActivity() {
}
}
- open fun showConnectionProblem(title: String?, message: String) {
+ open fun showConnectionProblem(errorCount: Int, title: String?, message: String) {
val alert = HabiticaAlertDialog(this)
alert.setTitle(title)
alert.setMessage(message)
@@ -226,17 +234,39 @@ abstract class BaseActivity : AppCompatActivity() {
open fun hideConnectionProblem() {
}
- fun shareContent(identifier: String, message: String, image: Bitmap? = null) {
- analyticsManager.logEvent("shared", bundleOf(Pair("identifier", identifier)))
+ fun shareContent(identifier: String, message: String?, image: Bitmap? = null) {
+ Analytics.sendEvent("shared", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("identifier" to identifier))
val sharingIntent = Intent(Intent.ACTION_SEND)
- sharingIntent.type = "*/*"
- sharingIntent.putExtra(Intent.EXTRA_TEXT, message)
+ sharingIntent.type = "image/*"
+ if (message?.isNotBlank() == true) {
+ sharingIntent.putExtra(Intent.EXTRA_TEXT, message)
+ }
if (image != null) {
- val path = MediaStore.Images.Media.insertImage(this.contentResolver, image, "${(Date())}", null)
- if (path != null) {
- val uri = Uri.parse(path)
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri)
+ val fos: OutputStream
+ val uri: Uri
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ val resolver = contentResolver
+ val contentValues = ContentValues()
+ contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "${Date()}.png")
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
+ contentValues.put(
+ MediaStore.MediaColumns.RELATIVE_PATH,
+ Environment.DIRECTORY_PICTURES
+ )
+ uri =
+ resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) ?: return
+ fos = resolver.openOutputStream(uri, "wt") ?: return
+ } else {
+ val imagesDir =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ .toString()
+ val file = File(imagesDir, "${Date()}.png")
+ uri = file.absoluteFile.toUri()
+ fos = FileOutputStream(file)
}
+ image.compress(Bitmap.CompressFormat.PNG, 100, fos)
+ fos.close()
+ sharingIntent.putExtra(Intent.EXTRA_STREAM, uri)
}
startActivity(Intent.createChooser(sharingIntent, getString(R.string.share_using)))
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt
index 3a5b74f45c..5d8c53bdb7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/BirthdayActivity.kt
@@ -59,8 +59,11 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.extensions.addCloseButton
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
@@ -129,6 +132,7 @@ class BirthdayActivity : BaseActivity() {
}) {
if ((userViewModel.user.value?.gemCount ?: 0) < 60) {
val dialog = InsufficientGemsDialog(this@BirthdayActivity, 3)
+ Analytics.sendEvent("show insufficient gems modal", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("reason" to "birthday"))
dialog.show()
return@launchCatching
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
index 6076f2445f..df9aebe373 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ChallengeFormActivity.kt
@@ -23,7 +23,7 @@ import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityCreateChallengeBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.tasks.Task
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
index fa1723cc26..2c42f7b15b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.activities
+import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
@@ -12,7 +13,9 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.navArgs
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityClassSelectionBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.extensions.observeOnce
+import com.habitrpg.android.habitica.helpers.ReviewManager
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.user.Gear
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Outfit
@@ -34,6 +37,9 @@ class ClassSelectionActivity : BaseActivity() {
@Inject
lateinit var userViewModel: MainUserViewModel
+ @Inject
+ lateinit var reviewManager: ReviewManager
+
private lateinit var binding: ActivityClassSelectionBinding
private var currentClass: String? = null
private var newClass: String = "healer"
@@ -280,6 +286,9 @@ class ClassSelectionActivity : BaseActivity() {
displayClassChanged(chosenClass)
}
}
+
+ // After class change was successful, check for in-app review eligibility the following check-in
+ checkForReviewPromptAfterClassSelection()
}
private fun displayProgressDialog(progressText: String): HabiticaProgressDialog {
@@ -292,4 +301,12 @@ class ClassSelectionActivity : BaseActivity() {
finish()
}
}
+
+ private fun checkForReviewPromptAfterClassSelection() {
+ userViewModel.user.observeOnce(this) { user ->
+ user?.loginIncentives?.let { totalCheckins ->
+ reviewManager.requestReview(this, totalCheckins)
+ }
+ }
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt
index 0a485cfac7..0c6afb282e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/DeathActivity.kt
@@ -1,31 +1,48 @@
package com.habitrpg.android.habitica.ui.activities
+import android.content.SharedPreferences
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
-import android.util.Log
import android.view.View
+import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
+import androidx.core.content.edit
import androidx.lifecycle.lifecycleScope
+import com.habitrpg.android.habitica.HabiticaApplication
+import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.ActivityDeathBinding
+import com.habitrpg.android.habitica.extensions.DateUtils
+import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.AdHandler
import com.habitrpg.android.habitica.helpers.AdType
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
+import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
+import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.ads.AdButton
import com.habitrpg.common.habitica.extensions.fromHtml
import com.habitrpg.common.habitica.helpers.Animations
import com.habitrpg.common.habitica.helpers.ExceptionHandler
+import com.habitrpg.common.habitica.helpers.launchCatching
import com.plattysoft.leonids.ParticleSystem
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
+import java.util.Calendar
+import java.util.Date
import javax.inject.Inject
@AndroidEntryPoint
-class DeathActivity : BaseActivity() {
+class DeathActivity : BaseActivity(), SnackbarActivity {
private lateinit var binding: ActivityDeathBinding
@Inject
@@ -37,6 +54,9 @@ class DeathActivity : BaseActivity() {
@Inject
lateinit var userViewModel: MainUserViewModel
+ @Inject
+ lateinit var sharedPreferences: SharedPreferences
+
override fun getLayoutResId(): Int = R.layout.activity_death
override fun getContentView(layoutResId: Int?): View {
@@ -55,7 +75,6 @@ class DeathActivity : BaseActivity() {
if (!it) {
return@AdHandler
}
- Log.d("AdHandler", "Reviving user")
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.updateUser("stats.hp", 1)
finish()
@@ -77,10 +96,86 @@ class DeathActivity : BaseActivity() {
binding.adButton.visibility = View.GONE
}
+ if (appConfigManager.enableFaintSubs()) {
+ userViewModel.user.observe(this) {
+ if (it?.isSubscribed == true && binding.reviveSubscriberWrapper.visibility != View.INVISIBLE) {
+ val lastRevive = Date(sharedPreferences.getLong("last_sub_revive", 0L))
+ if (DateUtils.isSameDay(Date(), lastRevive)) {
+ binding.reviveSubscriberWrapper.visibility = View.GONE
+ binding.subscriberBenefitUsedView.visibility = View.VISIBLE
+ lifecycleScope.launchCatching {
+ val date: Calendar = Calendar.getInstance()
+ date.set(Calendar.HOUR_OF_DAY, 0)
+ date.set(Calendar.MINUTE, 0)
+ date.set(Calendar.SECOND, 0)
+ date.add(Calendar.DAY_OF_MONTH, 1)
+ val midnight = date.time
+ while (true) {
+ binding.subscriberBenefitUsedView.text = getString(R.string.subscriber_benefit_used_faint, midnight.getShortRemainingString())
+ delay(1000L)
+ }
+ }
+ } else {
+ binding.reviveSubscriberWrapper.visibility = View.VISIBLE
+ binding.subscriberBenefitUsedView.visibility = View.GONE
+ }
+ binding.unsubbedWrapper.visibility = View.GONE
+ } else if (it?.isSubscribed == false) {
+ binding.reviveSubscriberWrapper.visibility = View.GONE
+ binding.unsubbedWrapper.visibility = View.VISIBLE
+ binding.subscribeModalButton.setOnClickListener {
+ Analytics.sendEvent("View death sub CTA", EventCategory.BEHAVIOUR, HitType.EVENT)
+ val subscriptionBottomSheet = EventOutcomeSubscriptionBottomSheetFragment().apply {
+ eventType = EventOutcomeSubscriptionBottomSheetFragment.EVENT_DEATH_SCREEN
+ }
+ subscriptionBottomSheet.show(supportFragmentManager, EventOutcomeSubscriptionBottomSheetFragment.TAG)
+ }
+ }
+ }
+ } else {
+ binding.reviveSubscriberWrapper.visibility = View.GONE
+ binding.unsubbedWrapper.visibility = View.GONE
+ binding.subscriberBenefitUsedView.visibility = View.GONE
+ }
+
+ binding.reviveSubscriberButton.setOnClickListener {
+ Analytics.sendEvent("second chance perk", EventCategory.BEHAVIOUR, HitType.EVENT)
+ sharedPreferences.edit {
+ putLong("last_sub_revive", Date().time)
+ }
+ lifecycleScope.launchCatching {
+ delay(300)
+ binding.reviveSubscriberWrapper.startAnimation(Animations.fadeOutAnimation())
+ }
+ lifecycleScope.launch(ExceptionHandler.coroutine()) {
+ userRepository.updateUser("stats.hp", 1)
+ MainScope().launchCatching {
+ delay(1000)
+ (HabiticaBaseApplication.getInstance(this@DeathActivity)?.currentActivity?.get() as? SnackbarActivity)?.let {activity ->
+ HabiticaSnackbar.showSnackbar(
+ activity.snackbarContainer(), getString(R.string.subscriber_benefit_success_faint), HabiticaSnackbar.SnackbarDisplayType.SUBSCRIBER_BENEFIT, isSubscriberBenefit = true, duration = 2500)
+ }
+ }
+ finish()
+ }
+ }
+
binding.restartButton.setOnClickListener {
binding.restartButton.isEnabled = false
lifecycleScope.launch(ExceptionHandler.coroutine()) {
- userRepository.revive()
+ val brokenItem = userRepository.revive()
+ if (brokenItem != null) {
+ MainScope().launchCatching {
+ delay(500)
+ (HabiticaBaseApplication.getInstance(this@DeathActivity)?.currentActivity?.get() as? SnackbarActivity)?.let { activity ->
+ HabiticaSnackbar.showSnackbar(
+ activity.snackbarContainer(),
+ getString(R.string.revive_broken_equipment, brokenItem.text),
+ HabiticaSnackbar.SnackbarDisplayType.BLACK
+ )
+ }
+ }
+ }
finish()
}
}
@@ -115,4 +210,8 @@ class DeathActivity : BaseActivity() {
override fun onBackPressed() {
moveTaskToBack(true)
}
+
+ override fun snackbarContainer(): ViewGroup {
+ return binding.snackbarContainer
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt
index 8876c7fa5f..99b477d375 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/FullProfileActivity.kt
@@ -27,8 +27,10 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityFullProfileBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.ReviewManager
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.UserStatComputer
+import com.habitrpg.android.habitica.interactors.ShareAvatarUseCase
import com.habitrpg.android.habitica.models.Achievement
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.members.Member
@@ -36,6 +38,7 @@ import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Permission
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter
+import com.habitrpg.android.habitica.ui.fragments.ReportBottomSheetFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.AppHeaderView
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
@@ -62,6 +65,7 @@ import kotlin.math.min
class FullProfileActivity : BaseActivity() {
private var blocks: List = listOf()
private var isModerator = false
+ private var isUserSupport = false
private var member: MutableState = mutableStateOf(null)
@Inject
@@ -76,6 +80,9 @@ class FullProfileActivity : BaseActivity() {
@Inject
lateinit var sharedPrefs: SharedPreferences
+ @Inject
+ lateinit var reviewManager: ReviewManager
+
private var userID = ""
private var username: String? = null
private var userDisplayName: String? = null
@@ -88,6 +95,7 @@ class FullProfileActivity : BaseActivity() {
private val dateFormatter = SimpleDateFormat.getDateInstance()
private lateinit var binding: ActivityFullProfileBinding
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupToolbar(binding.toolbar)
@@ -99,14 +107,9 @@ class FullProfileActivity : BaseActivity() {
setTitle(R.string.profile_loading_data)
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- refresh()
- }
-
binding.avatarWithBars.setContent {
HabiticaTheme {
- AppHeaderView(member.value, isMyProfile = isMyProfile()) {
- }
+ AppHeaderView(member.value, isMyProfile = isMyProfile(), onMemberRowClicked = {}, onClassSelectionClicked = {})
}
}
@@ -126,38 +129,40 @@ class FullProfileActivity : BaseActivity() {
bundleOf(Pair("userID", userID), Pair("username", null))
)
}
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
+ lifecycleScope.launchCatching {
userRepository.getUser()
.collect {
blocks = it?.inbox?.blocks ?: listOf()
binding.blockedDisclaimerView.visibility =
if (isUserBlocked()) View.VISIBLE else View.GONE
+ isUserSupport = it?.hasPermission(Permission.USER_SUPPORT) == true
isModerator = it?.hasPermission(Permission.MODERATOR) == true
- binding.adminStatusView.isVisible = isModerator
- if (isModerator) {
- val member = socialRepository.retrieveMember(userID, true)
- member?.stats = this@FullProfileActivity.member.value?.stats
- if (member != null) {
- updateView(member)
- }
- this@FullProfileActivity.member.value = member
+ if (isModerator || isUserSupport) {
+ binding.adminStatusView.isVisible = true
+ refresh(true)
}
invalidateOptionsMenu()
- }
- }
- if (!isMyProfile()) {
+ }
+ }
+ lifecycleScope.launchCatching {
+ refresh(false)
}
}
- private suspend fun refresh() {
- val member = socialRepository.retrieveMember(userID)
- if (member != null) {
+ private suspend fun refresh(fromHall: Boolean) {
+ val member = socialRepository.retrieveMember(userID, fromHall)
+ if (member != null && !fromHall) {
updateView(member)
+ this.member.value = member
+ if (isMyProfile() && member.loginIncentives > 10) {
+ reviewManager.requestReview(this@FullProfileActivity, member.loginIncentives)
+ }
+ } else if (member != null) {
+ updateAccountStatus(member)
}
- this@FullProfileActivity.member.value = member
}
override fun onDestroy() {
@@ -169,17 +174,24 @@ class FullProfileActivity : BaseActivity() {
val inflater = menuInflater
inflater.inflate(R.menu.menu_full_profile, menu)
MenuCompat.setGroupDividerEnabled(menu, true)
- val item = menu.findItem(R.id.block_user)
+ val blockItem = menu.findItem(R.id.block_user)
+ val shareItem = menu.findItem(R.id.share_avatar)
- if (isMyProfile()) item.isVisible = false
+ if (isMyProfile()) {
+ blockItem.isVisible = false
+ shareItem.isVisible = true
+ } else {
+ blockItem.isVisible = true
+ shareItem.isVisible = false
+ }
if (isUserBlocked()) {
- item?.title = getString(R.string.unblock_user)
+ blockItem?.title = getString(R.string.unblock_user)
} else {
- item?.title = getString(R.string.block)
+ blockItem?.title = getString(R.string.block)
}
menu.setGroupVisible(R.id.admin_items, isModerator)
- if (isModerator) {
+ if (isModerator || isUserSupport) {
menu.findItem(R.id.ban_user)?.title = getString(
if (member.value?.authentication?.blocked == true) {
R.string.unban_user
@@ -255,6 +267,14 @@ class FullProfileActivity : BaseActivity() {
}
true
}
+ R.id.report_player -> {
+ showReportUserBottomSheet(
+ userIdBeingReported = userID,
+ usernameBeingReported = username ?: "",
+ userDisplayName = userDisplayName ?: ""
+ )
+ true
+ }
R.id.ban_user -> {
banUser()
true
@@ -267,6 +287,22 @@ class FullProfileActivity : BaseActivity() {
muteUser()
true
}
+ R.id.share_avatar -> {
+ member.value?.let {
+ val usecase = ShareAvatarUseCase()
+ lifecycleScope.launchCatching {
+ usecase.callInteractor(
+ ShareAvatarUseCase.RequestValues(
+ this@FullProfileActivity,
+ it,
+ "Check out my avatar on Habitica!",
+ "avatar_profile"
+ )
+ )
+ }
+ }
+ true
+ }
else -> super.onOptionsItemSelected(item)
}
}
@@ -275,14 +311,16 @@ class FullProfileActivity : BaseActivity() {
val isMuted = member.value?.flags?.chatRevoked == true
val alert = HabiticaAlertDialog(this)
if (isMuted) {
- alert.setTitle(R.string.mute_user_confirm)
- } else {
alert.setTitle(R.string.unmute_user_confirm)
+ } else {
+ alert.setTitle(R.string.mute_user_confirm)
}
alert.addButton(R.string.yes, isPrimary = true, isDestructive = true) { _, _ ->
lifecycleScope.launchCatching {
- member.value?.id?.let { socialRepository.updateMember(it, "flags.chatRevoked", !isMuted) }
- refresh()
+ val flagsMap = mapOf("chatRevoked" to !isMuted)
+ val updateData = mapOf("flags" to flagsMap)
+ member.value?.id?.let { socialRepository.updateMember(it, updateData) }
+ refresh(true)
invalidateOptionsMenu()
}
}
@@ -290,35 +328,54 @@ class FullProfileActivity : BaseActivity() {
}
private fun shadowMuteUser() {
- val isBanned = member.value?.flags?.chatShadowMuted == true
+ val isShadowMuted = member.value?.flags?.chatShadowMuted == true
val alert = HabiticaAlertDialog(this)
- if (isBanned) {
- alert.setTitle(R.string.shadowmute_user_confirm)
- } else {
+ if (isShadowMuted) {
alert.setTitle(R.string.unshadowmute_user_confirm)
+ } else {
+ alert.setTitle(R.string.shadowmute_user_confirm)
}
alert.addButton(R.string.yes, isPrimary = true, isDestructive = true) { _, _ ->
lifecycleScope.launchCatching {
- member.value?.id?.let { socialRepository.updateMember(it, "flags.chatShadowMuted", !isBanned) }
- refresh()
+ val flagsMap = mapOf("chatShadowMuted" to !isShadowMuted)
+ val updateData = mapOf("flags" to flagsMap)
+ member.value?.id?.let { socialRepository.updateMember(it, updateData) }
+ refresh(true)
invalidateOptionsMenu()
}
}
alert.show()
}
+ private fun showReportUserBottomSheet(userIdBeingReported : String, usernameBeingReported: String, userDisplayName: String) {
+ val reportBottomSheetFragment = ReportBottomSheetFragment.newInstance(
+ reportType = ReportBottomSheetFragment.REPORT_TYPE_USER,
+ profileName = usernameBeingReported,
+ displayName = userDisplayName,
+ messageId = "",
+ messageText = "",
+ groupId = "",
+ userIdBeingReported = userIdBeingReported,
+ sourceView = this::class.simpleName ?: ""
+ )
+
+ reportBottomSheetFragment.show(supportFragmentManager, ReportBottomSheetFragment.TAG)
+ }
+
private fun banUser() {
val isBanned = member.value?.authentication?.blocked == true
val alert = HabiticaAlertDialog(this)
if (isBanned) {
- alert.setTitle(R.string.ban_user_confirm)
- } else {
alert.setTitle(R.string.unban_user_confirm)
+ } else {
+ alert.setTitle(R.string.ban_user_confirm)
}
alert.addButton(R.string.yes, isPrimary = true, isDestructive = true) { _, _ ->
lifecycleScope.launchCatching {
- member.value?.id?.let { socialRepository.updateMember(it, "auth.blocked", !isBanned) }
- refresh()
+ val flagsMap = mapOf("blocked" to !isBanned)
+ val updateData = mapOf("auth" to flagsMap)
+ member.value?.id?.let { socialRepository.updateMember(it, updateData) }
+ refresh(true)
invalidateOptionsMenu()
}
}
@@ -366,10 +423,8 @@ class FullProfileActivity : BaseActivity() {
supportActionBar?.subtitle = user.formattedUsername
val imageUrl = profile.imageUrl
- if (imageUrl == null || imageUrl.isEmpty()) {
+ if (imageUrl.isNullOrEmpty()) {
binding.profileImage.visibility = View.GONE
- } else {
- // binding.profileImage.load(imageUrl)
}
val blurbText = profile.blurb
@@ -386,18 +441,6 @@ class FullProfileActivity : BaseActivity() {
}
binding.totalCheckinsView.text = user.loginIncentives.toString()
- val status = mutableListOf()
- if (user.authentication?.blocked == true) status.add("Banned")
- if (user.flags?.chatShadowMuted == true) status.add("Shadow Muted")
- if (user.flags?.chatRevoked == true) status.add("Muted")
- if (status.isNotEmpty()) {
- binding.adminStatusTextview.text = status.joinToString(", ")
- binding.adminStatusTextview.setTextColor(ContextCompat.getColor(this, R.color.text_red))
- } else {
- binding.adminStatusTextview.text = getString(R.string.regular_access)
- binding.adminStatusTextview.setTextColor(ContextCompat.getColor(this, R.color.text_green))
- }
-
lifecycleScope.launchCatching {
loadItemDataByOutfit(user.equipped).collect { gear -> gotGear(gear, user) }
}
@@ -425,6 +468,20 @@ class FullProfileActivity : BaseActivity() {
if (user.currentMount?.isNotBlank() == true) binding.currentMountDrawee.loadImage("Mount_Icon_" + user.currentMount)
}
+ private fun updateAccountStatus(member: Member) {
+ val status = mutableListOf()
+ if (member.authentication?.blocked == true) status.add("Banned")
+ if (member.flags?.chatShadowMuted == true) status.add("Shadow Muted")
+ if (member.flags?.chatRevoked == true) status.add("Muted")
+ if (status.isNotEmpty()) {
+ binding.adminStatusTextview.text = status.joinToString(", ")
+ binding.adminStatusTextview.setTextColor(ContextCompat.getColor(this, R.color.text_red))
+ } else {
+ binding.adminStatusTextview.text = getString(R.string.regular_access)
+ binding.adminStatusTextview.setTextColor(ContextCompat.getColor(this, R.color.text_green))
+ }
+ }
+
// endregion
// region Attributes
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
index 7d5c4d000c..439ee7e825 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/LoginActivity.kt
@@ -37,8 +37,10 @@ import com.habitrpg.android.habitica.databinding.ActivityLoginBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
import com.habitrpg.android.habitica.ui.viewmodels.AuthenticationViewModel
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
@@ -296,7 +298,7 @@ class LoginActivity : BaseActivity() {
startSetupActivity()
} else {
startMainActivity()
- AmplitudeManager.sendEvent("login", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT)
+ Analytics.sendEvent("login", EventCategory.BEHAVIOUR, HitType.EVENT)
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
index e682987b96..030f735ed3 100755
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
@@ -4,6 +4,7 @@ import android.app.NotificationChannel
import android.app.NotificationManager
import android.appwidget.AppWidgetManager
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
@@ -17,13 +18,26 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.ActionBarDrawerToggle
+import androidx.appcompat.app.AlertDialog
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
import androidx.core.view.children
import androidx.core.view.setPadding
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
@@ -32,6 +46,7 @@ import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.google.firebase.perf.FirebasePerformance
import com.habitrpg.android.habitica.BuildConfig
+import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.InventoryRepository
@@ -45,13 +60,15 @@ import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HitType
-import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationOpenHandler
+import com.habitrpg.android.habitica.helpers.ReviewManager
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.helpers.collectAsStateLifecycleAware
import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase
import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
+import com.habitrpg.android.habitica.interactors.ShareAvatarUseCase
+import com.habitrpg.android.habitica.interactors.SharePetUseCase
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.models.user.UserQuestStatus
@@ -61,7 +78,10 @@ import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.android.habitica.ui.views.AppHeaderView
+import com.habitrpg.android.habitica.ui.views.ComposableAvatarView
import com.habitrpg.android.habitica.ui.views.GroupPlanMemberList
+import com.habitrpg.android.habitica.ui.views.HabiticaButton
+import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
import com.habitrpg.android.habitica.ui.views.dialogs.QuestCompletedDialog
import com.habitrpg.android.habitica.ui.views.showAsBottomSheet
@@ -75,6 +95,7 @@ import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.helpers.ExceptionHandler
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.views.AvatarView
import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse
@@ -120,6 +141,9 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
@Inject
internal lateinit var appConfigManager: AppConfigManager
+ @Inject
+ lateinit var reviewManager: ReviewManager
+
lateinit var binding: ActivityMainBinding
val snackbarContainer: ViewGroup
@@ -283,6 +307,67 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
user,
teamPlan = if (canShowTeamHeader) teamPlan else null,
teamPlanMembers = teamPlanMembers,
+ isMyProfile = true,
+ onAvatarClicked = {
+ showAsBottomSheet { dismiss ->
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ modifier = Modifier.padding(22.dp)
+ ) {
+ ComposableAvatarView(avatar = user)
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(15.dp),
+ ) {
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ modifier = Modifier.height(48.dp),
+ onClick = {
+ dismiss()
+ MainNavigationController.navigate(MainNavDirections.openProfileActivity(user?.id ?: ""))
+ }) {
+ Text(stringResource(id = R.string.open_profile))
+ }
+
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ modifier = Modifier.height(48.dp),
+ onClick = {
+ dismiss()
+ MainNavigationController.navigate(R.id.avatarOverviewFragment)
+ }) {
+ Text(stringResource(id = R.string.customize_avatar))
+ }
+
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ modifier = Modifier.height(48.dp),
+ onClick = {
+ dismiss()
+ user?.let {
+ val usecase = ShareAvatarUseCase()
+ lifecycleScope.launchCatching {
+ usecase.callInteractor(
+ ShareAvatarUseCase.RequestValues(
+ this@MainActivity,
+ it,
+ "Check out my avatar on Habitica!",
+ "avatar_bottomsheet"
+ )
+ )
+ }
+ }
+ }) {
+ Text(stringResource(id = R.string.share_avatar))
+ }
+ }
+ }
+ }
+ },
onMemberRowClicked = {
showAsBottomSheet { onClose ->
val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState(
@@ -536,6 +621,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
this.title = newTitle
}
}
+ checkForReviewPrompt(user)
}
}
@@ -606,7 +692,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
val now = Date().time
lifecycleScope.launch(context = Dispatchers.Main) {
delay(1000L)
- if (!this@MainActivity.isFinishing && MainNavigationController.isReady && now - lastDeathDialogDisplay > 60000) {
+ if (!this@MainActivity.isFinishing && MainNavigationController.isReady && now - lastDeathDialogDisplay > 10000) {
lastDeathDialogDisplay = now
MainNavigationController.navigate(R.id.deathActivity)
}
@@ -681,9 +767,11 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
private var errorJob: Job? = null
- override fun showConnectionProblem(title: String?, message: String) {
- if (title != null) {
- super.showConnectionProblem(title, message)
+ override fun showConnectionProblem(errorCount: Int, title: String?, message: String) {
+ if (errorCount == 1) {
+ showSnackbar(title = title, content = message, displayType = HabiticaSnackbar.SnackbarDisplayType.FAILURE)
+ } else if (title != null) {
+ super.showConnectionProblem(errorCount, title, message)
} else {
if (errorJob?.isCancelled == false) {
// a new error resets the timer to hide the error message
@@ -718,4 +806,9 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
binding.content.toolbarTitle.setPadding(0)
}
}
+
+ private fun checkForReviewPrompt(user: User) {
+ reviewManager.requestReview(this, user.loginIncentives)
+ }
+
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
index 93338e418d..69282cbcf5 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
@@ -19,7 +20,9 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding
import com.habitrpg.android.habitica.extensions.fadeInAnimation
+import com.habitrpg.android.habitica.extensions.flash
import com.habitrpg.android.habitica.extensions.observeOnce
+import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.common.habitica.extensions.fromHtml
@@ -39,6 +42,7 @@ import com.habitrpg.common.habitica.models.notifications.QuestInvitationData
import com.habitrpg.common.habitica.models.notifications.UnallocatedPointsData
import com.habitrpg.common.habitica.views.PixelArtView
import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
@@ -145,7 +149,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
Notification.Type.GROUP_TASK_APPROVED.type -> createGroupTaskApprovedNotification(it)
Notification.Type.GROUP_TASK_REQUIRES_APPROVAL.type -> createGroupTaskNeedsApprovalNotification(it)
Notification.Type.PARTY_INVITATION.type -> createPartyInvitationNotification(it)
- Notification.Type.GUILD_INVITATION.type -> createGuildInvitationNotification(it)
Notification.Type.QUEST_INVITATION.type -> createQuestInvitationNotification(it)
Notification.Type.ITEM_RECEIVED.type -> createItemReceivedNotification(it)
else -> null
@@ -198,7 +201,11 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
badge?.text = notificationCount.toString()
val dismissAllButton = header?.findViewById(R.id.dismiss_all_button) as? Button
- dismissAllButton?.setOnClickListener { viewModel.dismissAllNotifications(notifications) }
+ dismissAllButton?.setOnClickListener {
+ binding.root.flash()
+ HapticFeedbackManager.tap(it)
+ viewModel.dismissAllNotifications(notifications)
+ }
return header
}
@@ -222,25 +229,28 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
)
}
- private suspend fun createNewStuffNotification(notification: Notification): View? = withContext(ExceptionHandler.coroutine()) {
+ private suspend fun createNewStuffNotification(notification: Notification): View? = withContext(Dispatchers.IO) {
var baileyNotification = notification
val data = notification.data as? NewStuffData
val text = if (data?.title != null) {
fromHtml("" + getString(R.string.new_bailey_update) + " " + data.title)
} else {
- baileyNotification = userRepository.getNewsNotification() ?: return@withContext null
+ baileyNotification = userRepository.getNewsNotification() ?: notification
val baileyNewsData = baileyNotification.data as? NewStuffData
fromHtml("" + getString(R.string.new_bailey_update) + " " + baileyNewsData?.title)
}
baileyNotification.id = notification.id
- return@withContext createDismissableNotificationItem(
- baileyNotification,
- text,
- R.drawable.notifications_bailey
- )
+ return@withContext withContext(Dispatchers.Main) {
+ createDismissableNotificationItem(
+ baileyNotification,
+ text,
+ R.drawable.notifications_bailey
+ )
+ }
}
+
private fun createUnallocatedStatsNotification(notification: Notification): View? {
val level = userLvl ?: return null
return if (level >= 10) {
@@ -325,6 +335,8 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val container = item?.findViewById(R.id.notification_item) as? View
container?.setOnClickListener {
+ it.flash()
+ HapticFeedbackManager.tap(it)
val resultIntent = Intent()
resultIntent.putExtra("notificationId", notification.id)
setResult(Activity.RESULT_OK, resultIntent)
@@ -333,6 +345,8 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val dismissButton = item?.findViewById(R.id.dismiss_button) as? ImageView
dismissButton?.setOnClickListener {
+ container?.flash()
+ HapticFeedbackManager.tap(it)
removeNotificationAndRefresh(notification)
viewModel.dismissNotification(notification)
}
@@ -375,17 +389,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
}
}
- private fun createGuildInvitationNotification(notification: Notification): View? {
- val data = notification.data as? GuildInvitationData
- val stringId = if (data?.invitation?.publicGuild == false) R.string.invited_to_private_guild else R.string.invited_to_public_guild
-
- return createActionableNotificationItem(
- notification,
- fromHtml(getString(stringId, data?.invitation?.name)),
- data?.invitation?.publicGuild == true
- )
- }
-
private fun createQuestInvitationNotification(notification: Notification): View? {
val data = notification.data as? QuestInvitationData
@@ -445,6 +448,8 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
if (openable) {
val container = item?.findViewById(R.id.notification_item) as? View
container?.setOnClickListener {
+ it.flash()
+ HapticFeedbackManager.tap(it)
if (inviterId != null) {
FullProfileActivity.open(inviterId)
} else {
@@ -458,11 +463,17 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
val acceptButton = item?.findViewById(R.id.accept_button) as? Button
acceptButton?.setOnClickListener {
+ binding.root.flash()
+ HapticFeedbackManager.tap(it)
+ removeNotificationAndRefresh(notification)
viewModel.accept(notification.id)
}
val rejectButton = item?.findViewById(R.id.reject_button) as? Button
rejectButton?.setOnClickListener {
+ binding.root.flash()
+ HapticFeedbackManager.tap(it)
+ removeNotificationAndRefresh(notification)
viewModel.reject(notification.id)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
index 620a105e37..ff96722541 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/SetupActivity.kt
@@ -7,6 +7,7 @@ import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsetsController
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
@@ -22,11 +23,14 @@ import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivitySetupBinding
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.fragments.setup.AvatarSetupFragment
import com.habitrpg.android.habitica.ui.fragments.setup.TaskSetupFragment
import com.habitrpg.android.habitica.ui.fragments.setup.WelcomeFragment
+import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import com.viewpagerindicator.IconPagerAdapter
@@ -85,7 +89,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
}
val additionalData = HashMap()
additionalData["status"] = "displayed"
- AmplitudeManager.sendEvent("setup", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
+ Analytics.sendEvent("setup", EventCategory.BEHAVIOUR, HitType.EVENT, additionalData)
val currentDeviceLanguage = Locale.getDefault().language
for (language in resources.getStringArray(R.array.LanguageValues)) {
@@ -96,19 +100,26 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
}
}
- val window = window
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- val decor = getWindow().decorView
- decor.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- window.statusBarColor = ContextCompat.getColor(this, R.color.light_gray_bg)
- } else {
- window.statusBarColor = ContextCompat.getColor(this, R.color.days_gray)
- }
-
binding.viewPager.disableFading = true
binding.previousButton.setOnClickListener { previousClicked() }
binding.nextButton.setOnClickListener { nextClicked() }
+
+ if (this.isUsingNightModeResources()) {
+ window.statusBarColor = ContextCompat.getColor(this, R.color.black_20_alpha)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ window.insetsController?.setSystemBarsAppearance(
+ 0,
+ WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+ )
+ } else {
+ @Suppress("DEPRECATION")
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ }
+ }
+
+
}
override fun onDestroy() {
@@ -210,7 +221,7 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
if (completedSetup && !hasCompleted) {
val additionalData = HashMap()
additionalData["status"] = "completed"
- AmplitudeManager.sendEvent("setup", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
+ Analytics.sendEvent("setup", EventCategory.BEHAVIOUR, HitType.EVENT, additionalData)
hasCompleted = true
lifecycleScope.launchCatching {
userRepository.updateUser("flags.welcomed", true)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
index 25b795da23..d707537a21 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskFormActivity.kt
@@ -19,6 +19,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.CheckBox
+import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.AppCompatCheckBox
@@ -26,7 +27,6 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.toMutableStateList
import androidx.core.content.ContextCompat
-import androidx.core.os.bundleOf
import androidx.core.view.children
import androidx.core.view.forEachIndexed
import androidx.core.view.isVisible
@@ -80,6 +80,8 @@ import javax.inject.Inject
@AndroidEntryPoint
class TaskFormActivity : BaseActivity() {
+
+
private val viewModel: TaskFormViewModel by viewModels()
private lateinit var binding: ActivityTaskFormBinding
@@ -122,10 +124,14 @@ class TaskFormActivity : BaseActivity() {
val alert = HabiticaAlertDialog(this)
alert.setTitle(R.string.push_notification_system_settings_title)
alert.setMessage(R.string.push_notification_system_settings_description)
- alert.addButton(R.string.settings, true, false) { _, _ ->
- val notifSettingIntent: Intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .putExtra(Settings.EXTRA_APP_PACKAGE, applicationContext?.packageName)
+ alert.addButton(R.string.settings, isPrimary = true, isDestructive = false) { _, _ ->
+ val notifSettingIntent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(Settings.EXTRA_APP_PACKAGE, applicationContext?.packageName)
+ } else {
+ return@addButton
+ }
startActivity(notifSettingIntent)
}
alert.addButton(R.string.cancel, false) { _, _ ->
@@ -217,7 +223,8 @@ class TaskFormActivity : BaseActivity() {
.map { tagRepository.getUnmanagedCopy(it) }
.collect {
tags = it
- setTagViews()
+ sortTagPositions()
+ createTagViews()
}
}
userViewModel.user.observe(this) {
@@ -414,8 +421,7 @@ class TaskFormActivity : BaseActivity() {
val alert = HabiticaAlertDialog(this)
alert.setTitle(R.string.unsaved_changes)
alert.setMessage(R.string.discard_changes_to_task_message)
- alert.addButton(R.string.discard, true, true) { _, _ ->
- analyticsManager.logEvent("discard_task", bundleOf(Pair("is_creating", isCreating)))
+ alert.addButton(R.string.discard, isPrimary = true, isDestructive = true) { _, _ ->
super.onBackPressed()
}
alert.addButton(R.string.cancel, false) { _, _ ->
@@ -525,18 +531,65 @@ class TaskFormActivity : BaseActivity() {
}
}
- private fun setTagViews() {
+ private fun sortTagPositions() {
+ val sortedTagList = arrayListOf()
+ val challengeTagList = arrayListOf()
+ val groupTagList = arrayListOf()
+ val otherTagList = arrayListOf()
+ tags.forEach {
+ if (it.challenge) {
+ challengeTagList.add(it)
+ } else if (it.group != null) {
+ groupTagList.add(it)
+ } else {
+ otherTagList.add(it)
+ }
+ }
+ val challengesTagTitle = Tag().apply {
+ name = getString(R.string.challenge_tags)
+ }
+ val groupsTagTitle = Tag().apply {
+ name = getString(R.string.group_tags)
+ }
+ val otherTagTitle = Tag().apply {
+ name = getString(R.string.your_tags)
+ }
+ if (challengeTagList.isNotEmpty()) {
+ sortedTagList.add(challengesTagTitle)
+ sortedTagList.addAll(challengeTagList)
+ }
+ if (groupTagList.isNotEmpty()) {
+ sortedTagList.add(groupsTagTitle)
+ sortedTagList.addAll(groupTagList)
+ }
+ if (otherTagList.isNotEmpty()) {
+ sortedTagList.add(otherTagTitle)
+ sortedTagList.addAll(otherTagList)
+ }
+ tags = sortedTagList
+ }
+
+ private fun createTagViews() {
binding.tagsWrapper.removeAllViews()
val padding = 20.dpToPx(this)
- for (tag in tags) {
- val view = CheckBox(this)
- view.setPadding(padding, view.paddingTop, view.paddingRight, view.paddingBottom)
- view.text = tag.name
- view.setTextColor(getThemeColor(R.attr.textColorTintedPrimary))
- if (preselectedTags?.contains(tag.id) == true) {
- view.isChecked = true
+ tags.forEach { tag ->
+ if (tag.id.isBlank()) {
+ // This is a title for a tag group
+ val view = TextView(this)
+ view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
+ view.text = tag.name
+ view.setTextColor(getThemeColor(R.attr.textColorTintedPrimary))
+ binding.tagsWrapper.addView(view)
+ } else {
+ val view = CheckBox(this)
+ view.setPadding(padding, view.paddingTop, view.paddingRight, view.paddingBottom)
+ view.text = tag.name
+ view.setTextColor(getThemeColor(R.attr.textColorTintedPrimary))
+ if (preselectedTags?.contains(tag.id) == true) {
+ view.isChecked = true
+ }
+ binding.tagsWrapper.addView(view)
}
- binding.tagsWrapper.addView(view)
}
setAllTagSelections()
updateTagViewsColors()
@@ -608,7 +661,7 @@ class TaskFormActivity : BaseActivity() {
binding.habitResetStreakButtons.visibility = View.GONE
assignedIDs =
- task.group?.assignedUsersDetail?.map { it.assignedUserID }?.filterNotNull()
+ task.group?.assignedUsersDetail?.mapNotNull { it.assignedUserID }
?.toMutableStateList() ?: mutableStateListOf()
task.group?.assignedUsersDetail?.forEach {
it.completedDate?.let { date ->
@@ -684,7 +737,7 @@ class TaskFormActivity : BaseActivity() {
thisTask.tags = RealmList()
binding.tagsWrapper.forEachIndexed { index, view ->
val tagView = view as? CheckBox
- if (tagView?.isChecked == true) {
+ if (tagView?.isChecked == true && tags[index].id.isNotBlank()) {
thisTask.tags?.add(tags[index])
}
}
@@ -721,7 +774,7 @@ class TaskFormActivity : BaseActivity() {
val assignChanges = mapOf(
"assign" to mutableListOf(),
- "unassign" to mutableListOf()
+ "unassign" to mutableListOf()
)
if (groupID != null && thisTask.group?.groupID == null) {
thisTask.group = TaskGroupPlan()
@@ -746,9 +799,6 @@ class TaskFormActivity : BaseActivity() {
} else {
taskRepository.updateTaskInBackground(thisTask, assignChanges)
}
- if (isDiscardCancelled) {
- analyticsManager.logEvent("back_to_task", bundleOf(Pair("is_creating", isCreating)))
- }
if (thisTask.type == TaskType.DAILY || thisTask.type == TaskType.TODO) {
taskAlarmManager.scheduleAlarmsForTask(thisTask)
@@ -828,7 +878,7 @@ class TaskFormActivity : BaseActivity() {
lifecycleScope.launch(Dispatchers.Main) {
challengeRepository.leaveChallenge(it, "keep-all")
taskRepository.deleteTask(task?.id ?: "")
- userRepository.retrieveUser(true, true)
+ userRepository.retrieveUser(withTasks = true, forced = true)
}
}
}
@@ -840,7 +890,7 @@ class TaskFormActivity : BaseActivity() {
challenge?.let {
lifecycleScope.launch(Dispatchers.Main) {
challengeRepository.leaveChallenge(it, "remove-all")
- userRepository.retrieveUser(true, true)
+ userRepository.retrieveUser(withTasks = true, forced = true)
}
}
}
@@ -872,17 +922,17 @@ class TaskFormActivity : BaseActivity() {
) { _, _ ->
lifecycleScope.launch(Dispatchers.Main) {
taskRepository.unlinkAllTasks(task.challengeID, "keep-all")
- userRepository.retrieveUser(true, true)
+ userRepository.retrieveUser(withTasks = true, forced = true)
}
}
dialog.addButton(
getString(R.string.delete_x_tasks, taskCount),
- false,
- true
+ isPrimary = false,
+ isDestructive = true
) { _, _ ->
lifecycleScope.launch(Dispatchers.Main) {
taskRepository.unlinkAllTasks(task.challengeID, "remove-all")
- userRepository.retrieveUser(true, true)
+ userRepository.retrieveUser(withTasks = true, forced = true)
}
}
dialog.setExtraCloseButtonVisibility(View.VISIBLE)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt
index b439e5f6f3..21b48a8214 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/TaskSummaryActivity.kt
@@ -47,7 +47,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.TaskDescriptionBuilder
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt
index e257fc5049..66dcb087fd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationEquipmentRecyclerViewAdapter.kt
@@ -11,7 +11,7 @@ import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.CustomizationGridItemBinding
import com.habitrpg.android.habitica.databinding.DialogPurchaseCustomizationBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
@@ -82,7 +82,13 @@ class CustomizationEquipmentRecyclerViewAdapter : androidx.recyclerview.widget.R
fun bind(equipment: Equipment) {
this.equipment = equipment
- binding.imageView.loadImage("shop_" + this.equipment?.key)
+ if (equipment.key?.isNotBlank() == true) {
+ binding.imageView.loadImage("shop_" + equipment.key)
+ } else {
+ binding.imageView.bitmap = null
+ binding.imageView.tag = null
+ binding.imageView.setImageResource(R.drawable.empty_slot)
+ }
if (equipment.owned == true || equipment.value == 0.0) {
binding.buyButton.visibility = View.GONE
} else {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt
index 8817c23329..03096ec48a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/CustomizationRecyclerViewAdapter.kt
@@ -9,7 +9,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.CustomizationGridItemBinding
import com.habitrpg.android.habitica.databinding.CustomizationSectionFooterBinding
import com.habitrpg.android.habitica.databinding.CustomizationSectionHeaderBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.inventory.CustomizationSet
import com.habitrpg.android.habitica.models.shops.ShopItem
@@ -172,12 +172,13 @@ class CustomizationRecyclerViewAdapter() : androidx.recyclerview.widget.Recycler
fun bind(customization: Customization) {
this.customization = customization
- if (customization.type == "background" && customization.identifier == "") {
+ val imageName = customization.getIconName(userSize, hairColor)
+ if (imageName != null) {
+ binding.imageView.loadImage(imageName)
+ } else {
binding.imageView.bitmap = null
binding.imageView.tag = null
- binding.imageView.setImageResource(R.drawable.no_background)
- } else {
- binding.imageView.loadImage(customization.getIconName(userSize, hairColor))
+ binding.imageView.setImageResource(R.drawable.empty_slot)
}
if (customization.isUsable(ownedCustomizations.contains(customization.id))) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt
index fdf3d72bc0..1d608944e5 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ItemRecyclerAdapter.kt
@@ -6,8 +6,11 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ItemItemBinding
+import com.habitrpg.android.habitica.databinding.ShopAdBinding
+import com.habitrpg.android.habitica.models.BaseMainObject
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
@@ -25,17 +28,20 @@ import com.habitrpg.android.habitica.ui.views.dialogs.DetailDialog
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.extensions.localizedCapitalizeWithSpaces
+import com.habitrpg.common.habitica.helpers.setMarkdown
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
-class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter() {
+class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter() {
var user: User? = null
var isHatching: Boolean = false
var isFeeding: Boolean = false
var hatchingItem: Item? = null
var feedingPet: Pet? = null
var fragment: DialogFragment? = null
+ var itemType = ""
+ var itemText = ""
private var existingPets: List? = null
private var ownedPets: Map? = null
var items: Map? = null
@@ -52,14 +58,53 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter Unit)? = null
var onCreateNewParty: (() -> Unit)? = null
var onUseSpecialItem: ((SpecialItem) -> Unit)? = null
+ var onOpenShop: (() -> Unit)? = null
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
- return ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false))
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return if (viewType == 0) {
+ ItemViewHolder(ItemItemBinding.inflate(context.layoutInflater, parent, false))
+ } else {
+ ShopAdViewHolder(ShopAdBinding.inflate(context.layoutInflater, parent, false))
+ }
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ if (position < data.size) {
+ val ownedItem = data[position] as OwnedItem
+ (holder as? ItemViewHolder)?.bind(ownedItem, items?.get(ownedItem.key))
+ } else {
+ val typedHolder = (holder as? ShopAdViewHolder) ?: return
+ if (itemType == "quests") {
+ typedHolder.binding.imageView.setImageResource(R.drawable.icon_quests)
+ typedHolder.binding.titleView.text = context.getString(R.string.quests_footer_title)
+ typedHolder.binding.descriptionView.setMarkdown(context.getString(R.string.quests_footer_description))
+ } else {
+ typedHolder.binding.imageView.setImageResource(R.drawable.icon_shops)
+ typedHolder.binding.titleView.text = context.getString(R.string.item_footer_title, itemText)
+ typedHolder.binding.descriptionView.setMarkdown(when (itemType) {
+ "eggs" -> context.getString(R.string.eggs_footer_description)
+ "food" -> context.getString(R.string.food_footer_description)
+ "hatchingPotions" -> context.getString(R.string.hatchingPotions_footer_description)
+ else -> ""
+ })
+ }
+ typedHolder.itemView.setOnClickListener {
+ onOpenShop?.invoke()
+ }
+ }
}
- override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
- val ownedItem = data[position]
- holder.bind(ownedItem, items?.get(ownedItem.key))
+ override fun getItemCount(): Int {
+ val actualCount = super.getItemCount()
+ return actualCount + if (itemType == "special" || actualCount == 0 || itemType == "") 0 else 1
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ return if (position < data.size) {
+ 0
+ } else {
+ -1
+ }
}
fun setExistingPets(pets: List) {
@@ -72,6 +117,8 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter 0) {
menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.use_item)))
}
- } else if (ownedItem?.itemType == "special") {
- if ((ownedItem?.numberOwned ?: 0) > 0) {
- menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.use_item)))
- }
}
menu.setSelectionRunnable { index ->
if (item == null && ownedItem != null) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt
index f4329bc8b1..dceb4b8d07 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/PetDetailRecyclerAdapter.kt
@@ -20,9 +20,10 @@ import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.Animations
+import com.habitrpg.shared.habitica.models.responses.FeedResponse
class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() {
- var onFeed: ((Pet, Food?) -> Unit)? = null
+ var onFeed: (suspend (Pet, Food?) -> FeedResponse?)? = null
var onEquip: ((String) -> Unit)? = null
private var existingMounts: List? = null
private var ownedPets: Map? = null
@@ -114,7 +115,7 @@ class PetDetailRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapt
} else {
val pet = itemList[position] as Pet
if ((
- ownedPets?.get(pet.key ?: "")?.trained
+ ownedPets?.get(pet.key)?.trained
?: 0
) <= 0 && eggCount(pet) > 0 && potionCount(pet) > 0
) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt
index d1bd81b363..868d0ca2be 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/ShopRecyclerAdapter.kt
@@ -11,7 +11,9 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ShopArmoireGearBinding
import com.habitrpg.android.habitica.databinding.ShopHeaderBinding
import com.habitrpg.android.habitica.extensions.inflate
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopCategory
import com.habitrpg.android.habitica.models.shops.ShopItem
@@ -24,9 +26,11 @@ import com.habitrpg.android.habitica.ui.views.getTranslatedClassName
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
import com.habitrpg.common.habitica.extensions.fromHtml
import com.habitrpg.common.habitica.extensions.loadImage
+import com.habitrpg.common.habitica.helpers.MainNavigationController
class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter() {
+ var completedQuests: List = emptyList()
var armoireCount: Int = 0
var onNeedsRefresh: (() -> Unit)? = null
var onShowPurchaseDialog: ((ShopItem, Boolean) -> Unit)? = null
@@ -148,6 +152,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter
val dialog = InsufficientGemsDialog(activity, 3)
+ Analytics.sendEvent("show insufficient gems modal", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("reason" to "class change"))
dialog.show()
}
}
@@ -173,6 +178,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter (holder as? EmptyStateViewHolder)?.text = obj
is Pair<*, *> -> (holder as? ArmoireGearViewHolder)?.bind(obj.first as? String ?: "", obj.second as? Int ?: 0)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt
index 22065222ab..aa5452b962 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/inventory/StableRecyclerAdapter.kt
@@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ShopHeaderBinding
import com.habitrpg.android.habitica.extensions.inflate
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.Animal
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@@ -26,13 +26,14 @@ import com.habitrpg.android.habitica.ui.viewHolders.PetViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.SectionViewHolder
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
+import com.habitrpg.shared.habitica.models.responses.FeedResponse
class StableRecyclerAdapter : RecyclerView.Adapter() {
var shopSpriteSuffix: String? = null
private var eggs: Map = mapOf()
var animalIngredientsRetriever: ((Animal, ((Pair) -> Unit)) -> Unit)? = null
- var onFeed: ((Pet, Food?) -> Unit)? = null
+ var onFeed: (suspend (Pet, Food?) -> FeedResponse?)? = null
var itemType: String? = null
var currentPet: String? = null
set(value) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/GuildListAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/GuildListAdapter.kt
deleted file mode 100644
index 7aabd14dc1..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/social/GuildListAdapter.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.habitrpg.android.habitica.ui.adapter.social
-
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Filter
-import android.widget.Filterable
-import androidx.core.content.ContextCompat
-import androidx.core.os.bundleOf
-import androidx.recyclerview.widget.RecyclerView
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.SocialRepository
-import com.habitrpg.android.habitica.databinding.ItemPublicGuildBinding
-import com.habitrpg.android.habitica.databinding.ItemUserGuildBinding
-import com.habitrpg.android.habitica.databinding.PillTextviewBinding
-import com.habitrpg.android.habitica.extensions.inflate
-import com.habitrpg.android.habitica.helpers.MainNavigationController
-import com.habitrpg.android.habitica.models.social.Group
-import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
-import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
-import com.habitrpg.common.habitica.extensions.dpToPx
-import com.habitrpg.common.habitica.extensions.layoutInflater
-import com.habitrpg.common.habitica.helpers.EmojiParser
-import com.habitrpg.common.habitica.helpers.NumberAbbreviator
-import io.realm.Case
-import io.realm.OrderedRealmCollection
-import java.util.Locale
-
-class GuildListAdapter : BaseRecyclerViewAdapter(), Filterable {
-
- var socialRepository: SocialRepository? = null
-
- var onlyShowUsersGuilds = false
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return if (onlyShowUsersGuilds) {
- UserGuildViewHolder(parent.inflate(R.layout.item_user_guild))
- } else {
- GuildViewHolder(parent.inflate(R.layout.item_public_guild))
- }
- }
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- val guild = data[position]
- if (onlyShowUsersGuilds && holder is UserGuildViewHolder) {
- holder.bind(guild)
- } else if (holder is GuildViewHolder) {
- holder.bind(guild)
- holder.itemView.tag = guild
- }
- holder.itemView.setOnClickListener {
- MainNavigationController.navigate(R.id.guildFragment, bundleOf(Pair("groupID", guild.id)))
- }
- }
-
- private var unfilteredData: List? = null
-
- fun setUnfilteredData(data: List?) {
- this.data = data ?: emptyList()
- unfilteredData = data
- }
-
- override fun getFilter(): Filter {
- return object : Filter() {
- override fun performFiltering(constraint: CharSequence): FilterResults {
- val results = FilterResults()
- results.values = constraint
- return FilterResults()
- }
-
- override fun publishResults(constraint: CharSequence, results: FilterResults) {
- unfilteredData?.let {
- if (constraint.isNotEmpty() && it is OrderedRealmCollection) {
- data = it.where()
- .beginGroup()
- .contains("name", constraint.toString(), Case.INSENSITIVE)
- .or()
- .contains("summary", constraint.toString(), Case.INSENSITIVE)
- .endGroup()
- .findAll()
- } else {
- data = it
- }
- }
- }
- }
- }
-
- class UserGuildViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val binding = ItemUserGuildBinding.bind(itemView)
-
- fun bind(guild: Group) {
- binding.titleTextView.text = guild.name
-
- val number = when {
- guild.memberCount < 1000 -> 2
- guild.memberCount < 10000 -> 1
- else -> 0
- }
- val formattedNumber = NumberAbbreviator.abbreviate(itemView.context, guild.memberCount.toDouble(), number)
- binding.guildBadgeView.setImageBitmap(
- HabiticaIconsHelper.imageOfGuildCrest(
- false,
- false,
- guild.memberCount.toFloat(),
- formattedNumber
- )
- )
- }
- }
-
- class GuildViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- val binding = ItemPublicGuildBinding.bind(itemView)
-
- fun bind(guild: Group) {
- binding.nameTextView.text = guild.name
- val number = when {
- guild.memberCount < 1000 -> 2
- guild.memberCount < 10000 -> 1
- else -> 0
- }
- binding.memberCountTextView.text = NumberAbbreviator.abbreviate(itemView.context, guild.memberCount.toDouble(), number)
- binding.descriptionTextView.text = EmojiParser.parseEmojis(guild.summary)
- binding.descriptionTextView.setOnClickListener {
- itemView.callOnClick()
- }
- binding.guildBadgeView.setImageBitmap(HabiticaIconsHelper.imageOfGuildCrestSmall(guild.memberCount.toFloat()))
-
- binding.tagWrapper.removeAllViews()
- guild.categories?.forEach { category ->
- val textView = PillTextviewBinding.inflate(itemView.context.layoutInflater, binding.tagWrapper, true)
- textView.root.text = category.name?.split("_")?.joinToString(" ") {
- it.replaceFirstChar {
- if (it.isLowerCase()) {
- it.titlecase(
- Locale.getDefault()
- )
- } else {
- it.toString()
- }
- }
- }
- textView.root.background = if (category.slug == "habitica_official") {
- textView.root.setTextColor(ContextCompat.getColor(itemView.context, R.color.white))
- ContextCompat.getDrawable(itemView.context, R.drawable.pill_bg_purple_400)
- } else {
- textView.root.setTextColor(ContextCompat.getColor(itemView.context, R.color.text_secondary))
- ContextCompat.getDrawable(itemView.context, R.drawable.pill_bg_gray)
- }
- val hPadding = 10.dpToPx(itemView.context)
- val vPadding = 3.dpToPx(itemView.context)
- textView.root.setPadding(hPadding, vPadding, hPadding, vPadding)
- }
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt
index f848263b47..5902c9c5c4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/adapter/tasks/RewardsRecyclerViewAdapter.kt
@@ -39,6 +39,7 @@ class RewardsRecyclerViewAdapter(
override var adventureGuideOpenEvents: ((Boolean) -> Unit)? = null
var purchaseCardEvents: ((ShopItem) -> Unit)? = null
var onShowPurchaseDialog: ((ShopItem, Boolean) -> Unit)? = null
+ var goldGemsLeft: Int? = null
override var taskDisplayMode: String = "standard"
set(value) {
@@ -97,6 +98,9 @@ class RewardsRecyclerViewAdapter(
} else if (inAppRewards != null) {
val item = inAppRewards?.get(position - customRewardCount) ?: return
if (holder is ShopItemViewHolder) {
+ if (item.key == "gem") {
+ holder.limitedNumberLeft = goldGemsLeft
+ }
holder.bind(item, item.canAfford(user, 1), 0)
holder.isPinned = true
holder.hidePinIndicator()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt
index 441d2ff77d..c6b04feeda 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt
@@ -14,7 +14,7 @@ import com.google.firebase.analytics.FirebaseAnalytics
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentAboutBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.plattysoft.leonids.ParticleSystem
import dagger.hilt.android.AndroidEntryPoint
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt
index fc2fb58ff7..95f0bc436a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseDialogFragment.kt
@@ -8,7 +8,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.habitrpg.android.habitica.data.TutorialRepository
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.common.habitica.helpers.launchCatching
import kotlinx.coroutines.delay
@@ -39,9 +39,7 @@ abstract class BaseDialogFragment : BottomSheetDialogFragment(
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- val additionalData = HashMap()
- additionalData["page"] = this.javaClass.simpleName
- AmplitudeManager.sendEvent("navigate", AmplitudeManager.EVENT_CATEGORY_NAVIGATION, AmplitudeManager.EVENT_HITTYPE_PAGEVIEW, additionalData)
+ Analytics.sendNavigationEvent(this.javaClass.simpleName)
binding = createBinding(inflater, container)
return binding?.root
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt
index b9428f7cd5..9ad62c4abd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseFragment.kt
@@ -9,7 +9,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.habitrpg.android.habitica.data.TutorialRepository
import com.habitrpg.android.habitica.ui.activities.MainActivity
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.launchCatching
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
@@ -25,9 +24,6 @@ abstract class BaseFragment : Fragment() {
@Inject
lateinit var tutorialRepository: TutorialRepository
- @Inject
- lateinit var analyticsManager: AnalyticsManager
-
var tutorialStepIdentifier: String? = null
protected var tutorialCanBeDeferred = true
var tutorialTexts: List = ArrayList()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt
index a4372052a6..8f8cb3ca33 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NavigationDrawerFragment.kt
@@ -19,6 +19,7 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.SimpleItemAnimator
+import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.data.InventoryRepository
@@ -29,7 +30,7 @@ import com.habitrpg.android.habitica.extensions.getMinuteOrSeconds
import com.habitrpg.android.habitica.extensions.getRemainingString
import com.habitrpg.android.habitica.extensions.getShortRemainingString
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.WorldStateEvent
import com.habitrpg.android.habitica.models.inventory.Item
import com.habitrpg.android.habitica.models.promotions.HabiticaPromotion
@@ -259,10 +260,7 @@ class NavigationDrawerFragment : DialogFragment() {
private fun updateUser(user: User) {
binding?.avatarView?.setOnClickListener {
- MainNavigationController.navigate(
- R.id.openProfileActivity,
- bundleOf(Pair("userID", user.id))
- )
+ MainNavigationController.navigate(MainNavDirections.openProfileActivity(user.id ?: ""))
}
setMessagesCount(user.inbox)
@@ -270,14 +268,6 @@ class NavigationDrawerFragment : DialogFragment() {
setDisplayName(user.profile?.name)
setUsername(user.formattedUsername)
binding?.avatarView?.setAvatar(user)
- binding?.questMenuView?.configure(user)
-
- val tavernItem = getItemWithIdentifier(SIDEBAR_TAVERN)
- if (user.preferences?.sleep == true) {
- tavernItem?.subtitle = context?.getString(R.string.damage_paused)
- } else {
- tavernItem?.subtitle = null
- }
val userItems = user.items
var hasSpecialItems = false
@@ -504,24 +494,6 @@ class NavigationDrawerFragment : DialogFragment() {
context.getString(R.string.sidebar_party)
)
)
- if (!configManager.hideTavern()) {
- items.add(
- HabiticaDrawerItem(
- R.id.tavernFragment,
- SIDEBAR_TAVERN,
- context.getString(R.string.sidebar_tavern)
- )
- )
- }
- if (!configManager.hideGuilds()) {
- items.add(
- HabiticaDrawerItem(
- R.id.guildOverviewFragment,
- SIDEBAR_GUILDS,
- context.getString(R.string.sidebar_guilds)
- )
- )
- }
if (!configManager.hideChallenges()) {
items.add(
HabiticaDrawerItem(
@@ -813,9 +785,7 @@ class NavigationDrawerFragment : DialogFragment() {
const val SIDEBAR_STATS = "stats"
const val SIDEBAR_ACHIEVEMENTS = "achievements"
const val SIDEBAR_SOCIAL = "social"
- const val SIDEBAR_TAVERN = "tavern"
const val SIDEBAR_PARTY = "party"
- const val SIDEBAR_GUILDS = "guilds"
const val SIDEBAR_CHALLENGES = "challenges"
const val SIDEBAR_INVENTORY = "inventory"
const val SIDEBAR_SHOPS_MARKET = "market"
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt
index 508838cd4b..81e5b8053a 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/NewsFragment.kt
@@ -12,7 +12,7 @@ import android.webkit.WebViewClient
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentNewsBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/ReportBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/ReportBottomSheetFragment.kt
new file mode 100644
index 0000000000..010d4ca3a7
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/ReportBottomSheetFragment.kt
@@ -0,0 +1,204 @@
+package com.habitrpg.android.habitica.ui.fragments
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.text.Spannable
+import android.text.SpannableStringBuilder
+import android.text.style.TypefaceSpan
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.data.SocialRepository
+import com.habitrpg.android.habitica.databinding.FragmentReportMessageBinding
+import com.habitrpg.common.habitica.helpers.ExceptionHandler
+import com.habitrpg.common.habitica.helpers.launchCatching
+import com.habitrpg.common.habitica.helpers.setMarkdown
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class ReportBottomSheetFragment : BottomSheetDialogFragment() {
+
+ private lateinit var binding: FragmentReportMessageBinding
+
+ @Inject
+ lateinit var socialRepository: SocialRepository
+
+ private var reportType: String? = null
+ private var messageID: String? = null
+ private var messageText: String? = null
+ private var profileName: String? = null
+ private var displayName: String? = null
+ private var reportingUserId: String? = null
+ private var groupID: String? = null
+ private var isReporting: Boolean = false
+ private var source: String? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentReportMessageBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
+ bottomSheetDialog.setOnShowListener { dialog: DialogInterface ->
+ val notificationDialog = dialog as BottomSheetDialog
+ notificationDialog.behavior.state = BottomSheetBehavior.STATE_EXPANDED
+ notificationDialog.behavior.isDraggable = false
+ }
+ return bottomSheetDialog
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ reportType = arguments?.getString(REPORT_TYPE)
+ messageID = arguments?.getString(MESSAGE_ID)
+ groupID = arguments?.getString(GROUP_ID)
+ messageText = arguments?.getString(MESSAGE_TEXT)
+ profileName = arguments?.getString(PROFILE_NAME)
+ displayName = arguments?.getString(DISPLAY_NAME)
+ reportingUserId = arguments?.getString(REPORTING_USER_ID)
+ source = arguments?.getString(SOURCE_VIEW)
+
+
+ binding.messageTextView.text = arguments?.getString(messageText)
+ binding.reportButton.setOnClickListener {
+ if (reportType == REPORT_TYPE_MESSAGE)
+ reportMessage()
+ else if (reportType == REPORT_TYPE_USER)
+ reportUser()
+ }
+
+ if (reportType == REPORT_TYPE_USER) {
+ binding.toolbarTitle.text = getString(R.string.report_player)
+ binding.additionalExplanationTextview.visibility = View.VISIBLE
+ binding.additionalInfoEdittext.hint = getString(R.string.report_hint)
+ binding.additionalExplanationTextview.setMarkdown(
+ getString(
+ R.string.report_user_description,
+ profileName
+ )
+ )
+ binding.reportExplanationTextview.setMarkdown(getString(R.string.report_user_explanation))
+ val formattedString = getString(R.string.report_formatted_name, displayName, profileName)
+ val spannable = SpannableStringBuilder(formattedString)
+ spannable.setSpan(
+ TypefaceSpan("sans-serif-medium"),
+ 0,
+ displayName?.length ?: 0,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ spannable.setSpan(
+ TypefaceSpan("sans-serif"),
+ displayName?.length ?: 0,
+ formattedString.length,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ binding.messageTextView.text = spannable
+ binding.reportReasonTitle.text = getString(R.string.report_reason_title_player)
+ } else if (reportType == REPORT_TYPE_MESSAGE) {
+ binding.toolbarTitle.text = getString(R.string.report_message)
+ binding.additionalExplanationTextview.visibility = View.GONE
+ binding.additionalInfoEdittext.hint = getString(R.string.report_message_hint)
+ binding.reportExplanationTextview.setMarkdown(getString(R.string.report_message_explanation))
+ binding.messageTextView.text = messageText
+ binding.reportReasonTitle.text = getString(R.string.report_reason_title_message)
+ }
+ }
+
+ private fun reportMessage() {
+ if (isReporting) {
+ return
+ }
+ isReporting = true
+ messageID?.let {
+ lifecycleScope.launch(
+ ExceptionHandler.coroutine {
+ isReporting = false
+ }
+ ) {
+ socialRepository.flagMessage(
+ messageID ?: "",
+ binding.additionalInfoEdittext.text.toString(),
+ groupID
+ )
+ dismiss()
+ }
+ }
+ }
+
+ private fun reportUser() {
+ if (isReporting) {
+ return
+ }
+ val userIdBeingReported = reportingUserId
+ if (userIdBeingReported.isNullOrBlank()) {
+ return
+ }
+ isReporting = true
+ lifecycleScope.launchCatching {
+ val reportReasonInfo = binding.additionalInfoEdittext.text.toString()
+ val updateData = mapOf(
+ "comment" to reportReasonInfo,
+ "source" to (source ?: "")
+ )
+ socialRepository.reportMember(userIdBeingReported, updateData)
+ socialRepository.blockMember(userIdBeingReported)
+ Toast.makeText(context, "$profileName Reported", Toast.LENGTH_SHORT).show()
+ dismiss()
+ }
+ }
+
+ companion object {
+ const val TAG = "ReportBottomSheetFragment"
+ const val REPORT_TYPE_MESSAGE = "report_type_message"
+ const val REPORT_TYPE_USER = "report_type_user"
+
+ private const val REPORTING_USER_ID = "reporting_user_id"
+ private const val REPORT_TYPE = "report_type"
+ private const val PROFILE_NAME = "profile_name"
+ private const val DISPLAY_NAME = "display_name"
+ private const val MESSAGE_ID = "message_id"
+ private const val MESSAGE_TEXT = "message_text"
+ private const val GROUP_ID = "group_id"
+ private const val SOURCE_VIEW = "source_view"
+
+
+ fun newInstance(
+ reportType: String,
+ profileName: String = "",
+ displayName: String = "",
+ userIdBeingReported: String,
+ messageId: String = "",
+ messageText: String,
+ groupId: String,
+ sourceView: String
+ ): ReportBottomSheetFragment {
+ val args = Bundle()
+ args.putString(REPORT_TYPE, reportType)
+ args.putString(PROFILE_NAME, profileName)
+ args.putString(DISPLAY_NAME, displayName)
+ args.putString(REPORTING_USER_ID, userIdBeingReported)
+ args.putString(MESSAGE_ID, messageId)
+ args.putString(MESSAGE_TEXT, messageText)
+ args.putString(GROUP_ID, groupId)
+ args.putString(SOURCE_VIEW, sourceView)
+ val fragment = ReportBottomSheetFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt
index 95e53f6aa7..cba8e33726 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarCustomizationFragment.kt
@@ -25,6 +25,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.BottomSheetBackgroundsFilterBinding
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
import com.habitrpg.android.habitica.extensions.setTintWith
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.models.CustomizationFilter
import com.habitrpg.android.habitica.models.inventory.Customization
import com.habitrpg.android.habitica.models.user.OwnedCustomization
@@ -148,6 +149,8 @@ class AvatarCustomizationFragment :
Log.e("NewFilter", it.toString())
}
}
+
+ Analytics.sendNavigationEvent("$type screen")
}
override fun onDestroy() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt
index e4f7db810c..6c8ddb1d27 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarEquipmentFragment.kt
@@ -10,6 +10,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.CustomizationEquipmentRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
@@ -97,6 +98,8 @@ class AvatarEquipmentFragment :
this.loadEquipment()
userViewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
+
+ Analytics.sendNavigationEvent("$type screen")
}
private fun loadEquipment() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt
index 16ceec178d..8ec32e4d8c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/customization/AvatarOverviewFragment.kt
@@ -2,6 +2,9 @@ package com.habitrpg.android.habitica.ui.fragments.inventory.customization
import android.os.Bundle
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.AdapterView
@@ -21,13 +24,16 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.core.graphics.scale
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.map
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentComposeScrollingBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.interactors.ShareAvatarUseCase
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.Equipment
+import com.habitrpg.android.habitica.ui.activities.BaseActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
@@ -35,6 +41,7 @@ import com.habitrpg.android.habitica.ui.views.SegmentedControl
import com.habitrpg.android.habitica.ui.views.equipment.AvatarCustomizationOverviewView
import com.habitrpg.android.habitica.ui.views.equipment.EquipmentOverviewView
import com.habitrpg.common.habitica.helpers.launchCatching
+import com.habitrpg.common.habitica.views.AvatarView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
@@ -129,6 +136,30 @@ open class AvatarOverviewFragment :
MainNavigationController.navigate(AvatarOverviewFragmentDirections.openEquipmentDetail(type, isCostume, equipped ?: ""))
}
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ super.onCreateOptionsMenu(menu, inflater)
+ inflater.inflate(R.menu.menu_share_avatar, menu)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ if (item.itemId == R.id.share_avatar) {
+ userViewModel.user.value?.let {
+ val usecase = ShareAvatarUseCase()
+ lifecycleScope.launchCatching {
+ usecase.callInteractor(
+ ShareAvatarUseCase.RequestValues(
+ requireActivity() as BaseActivity,
+ it,
+ "Check out my avatar on Habitica!",
+ "avatar_customization"
+ )
+ )
+ }
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
val newSize: String = if (position == 0) "slim" else "broad"
@@ -150,7 +181,7 @@ fun AvatarOverviewView(
costumeTwoHanded: Boolean = false,
onCustomizationTap: (String, String?) -> Unit,
onAvatarEquipmentTap: (String, String?) -> Unit,
- onEquipmentTap: (String, String?, Boolean) -> Unit
+ onEquipmentTap: (String, String?, Boolean) -> Unit,
) {
val user by userViewModel.user.observeAsState()
Column(
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt
index ac511f87ab..9b6ccab9d3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/equipment/EquipmentDetailFragment.kt
@@ -11,10 +11,13 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.extensions.observeOnce
+import com.habitrpg.android.habitica.helpers.ReviewManager
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
+import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
@@ -31,6 +34,11 @@ class EquipmentDetailFragment :
lateinit var inventoryRepository: InventoryRepository
override var binding: FragmentRefreshRecyclerviewBinding? = null
+ @Inject
+ lateinit var userViewModel: MainUserViewModel
+
+ @Inject
+ lateinit var reviewManager: ReviewManager
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRefreshRecyclerviewBinding {
return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false)
@@ -50,6 +58,14 @@ class EquipmentDetailFragment :
adapter.onEquip = {
lifecycleScope.launchCatching {
inventoryRepository.equipGear(it, isCostume ?: false)
+
+ userViewModel.user.observeOnce(viewLifecycleOwner) { user ->
+ val parentActivity = mainActivity
+ val totalCheckIns = user?.loginIncentives
+ if (totalCheckIns != null && parentActivity != null) {
+ reviewManager.requestReview(parentActivity, totalCheckIns)
+ }
+ }
}
}
return super.onCreateView(inflater, container, savedInstanceState)
@@ -71,7 +87,6 @@ class EquipmentDetailFragment :
getString(R.string.empty_title),
getString(R.string.empty_equipment_description),
null,
- getString(R.string.open_market)
) {
MainNavigationController.navigate(R.id.marketFragment)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt
index 4cc0c453c4..d2c7286089 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemDialogFragment.kt
@@ -13,7 +13,10 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentItemsDialogBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
@@ -35,6 +38,7 @@ import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
+import com.habitrpg.shared.habitica.models.responses.FeedResponse
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
@@ -68,6 +72,7 @@ class ItemDialogFragment : BaseDialogFragment() {
var itemTypeText: String? = null
var isHatching: Boolean = false
var isFeeding: Boolean = false
+ var onFeedResult: ((FeedResponse?) -> Unit)? = null
internal var hatchingItem: Item? = null
var feedingPet: Pet? = null
var user: User? = null
@@ -108,18 +113,40 @@ class ItemDialogFragment : BaseDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- binding?.recyclerView?.emptyItem = EmptyItem(
- getString(R.string.empty_items, itemTypeText ?: itemType),
- getString(R.string.open_market)
- ) {
- openMarket()
+ val buttonMethod = {
+ Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
+ "area" to "empty",
+ "type" to (itemType ?: "")
+ ))
+ if (itemType == "quests") {
+ MainNavigationController.navigate(R.id.questShopFragment)
+ } else {
+ openMarket()
+ }
}
+ binding?.recyclerView?.emptyItem = EmptyItem(
+ getString(R.string.no_x, itemTypeText ?: itemType),
+ when (itemType) {
+ "food" -> getString(R.string.empty_food_description)
+ "quests" -> getString(R.string.empty_quests_description)
+ "special" -> getString(R.string.empty_special_description_subscribed)
+ "eggs" -> getString(R.string.empty_eggs_description)
+ "hatchingPotions" -> getString(R.string.empty_potions_description)
+ else -> ""
+ },
+ when (itemType) {
+ "eggs" -> R.drawable.icon_eggs
+ "hatchingPotions" -> R.drawable.icon_hatchingpotions
+ "food" -> R.drawable.icon_food
+ "quests" -> R.drawable.icon_quests
+ "special" -> R.drawable.icon_special
+ else -> null
+ },
+ false,
+ if (itemType == "special") null else buttonMethod)
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding?.recyclerView?.layoutManager = layoutManager
- activity?.let {
- binding?.recyclerView?.addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(it, androidx.recyclerview.widget.DividerItemDecoration.VERTICAL))
- }
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
setAdapter()
@@ -138,29 +165,16 @@ class ItemDialogFragment : BaseDialogFragment() {
this.isHatching -> {
binding?.titleTextView?.text = getString(R.string.hatch_with, this.hatchingItem?.text)
binding?.titleTextView?.visibility = View.VISIBLE
- binding?.footerTextView?.text = getString(R.string.hatching_market_info)
- binding?.footerTextView?.visibility = View.VISIBLE
- binding?.openMarketButton?.visibility = View.VISIBLE
}
this.isFeeding -> {
binding?.titleTextView?.text = getString(R.string.dialog_feeding, this.feedingPet?.text)
binding?.titleTextView?.visibility = View.VISIBLE
- binding?.footerTextView?.text = getString(R.string.feeding_market_info)
- binding?.footerTextView?.visibility = View.VISIBLE
- binding?.openMarketButton?.visibility = View.VISIBLE
}
else -> {
binding?.titleTextView?.visibility = View.GONE
- binding?.footerTextView?.visibility = View.GONE
- binding?.openMarketButton?.visibility = View.GONE
}
}
- binding?.openMarketButton?.setOnClickListener {
- dismiss()
- openMarket()
- }
-
this.loadItems()
}
@@ -216,6 +230,18 @@ class ItemDialogFragment : BaseDialogFragment() {
}
adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) }
adapter?.onFeedPet = { food -> feedPet(food) }
+
+ adapter?.onOpenShop = {
+ Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
+ "area" to "bottom",
+ "type" to (itemType ?: "")
+ ))
+ if (itemType == "quests") {
+ MainNavigationController.navigate(R.id.questShopFragment)
+ } else {
+ openMarket()
+ }
+ }
}
}
@@ -223,13 +249,14 @@ class ItemDialogFragment : BaseDialogFragment() {
val pet = feedingPet ?: return
val activity = activity ?: return
activity.lifecycleScope.launchCatching {
- feedPetUseCase.callInteractor(
+ val result = feedPetUseCase.callInteractor(
FeedPetUseCase.RequestValues(
pet,
food,
activity
)
)
+ onFeedResult?.invoke(result)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt
index 2ff326051e..31375f48dd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/items/ItemRecyclerFragment.kt
@@ -17,7 +17,10 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentItemsBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.observeOnce
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@@ -89,25 +92,46 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ if (savedInstanceState != null) {
+ this.itemType = savedInstanceState.getString(ITEM_TYPE_KEY, "")
+ this.itemTypeText = savedInstanceState.getString(ITEM_TYPE_TEXT_KEY, "")
+ }
+
binding?.refreshLayout?.setOnRefreshListener(this)
- binding?.recyclerView?.emptyItem = EmptyItem(
- getString(R.string.empty_items, itemTypeText ?: itemType),
- null,
- null,
- if (itemType == "special") null else getString(R.string.open_shop)
- ) {
+ val buttonMethod = {
+ Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
+ "area" to "empty",
+ "type" to (itemType ?: "")
+ ))
if (itemType == "quests") {
MainNavigationController.navigate(R.id.questShopFragment)
} else {
openMarket()
}
}
+ binding?.recyclerView?.emptyItem = EmptyItem(
+ getString(R.string.no_x, itemTypeText ?: itemType),
+ when (itemType) {
+ "food" -> getString(R.string.empty_food_description)
+ "quests" -> getString(R.string.empty_quests_description)
+ "special" -> getString(R.string.empty_special_description_subscribed)
+ "eggs" -> getString(R.string.empty_eggs_description)
+ "hatchingPotions" -> getString(R.string.empty_potions_description)
+ else -> ""
+ },
+ when (itemType) {
+ "eggs" -> R.drawable.icon_eggs
+ "hatchingPotions" -> R.drawable.icon_hatchingpotions
+ "food" -> R.drawable.icon_food
+ "quests" -> R.drawable.icon_quests
+ "special" -> R.drawable.icon_special
+ else -> null
+ },
+ false,
+ if (itemType == "special") null else buttonMethod)
layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
binding?.recyclerView?.layoutManager = layoutManager
- activity?.let {
- binding?.recyclerView?.addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(it, androidx.recyclerview.widget.DividerItemDecoration.VERTICAL))
- }
binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
userViewModel.user.observeOnce(this) {
@@ -117,17 +141,8 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL
}
}
- if (savedInstanceState != null) {
- this.itemType = savedInstanceState.getString(ITEM_TYPE_KEY, "")
- }
binding?.titleTextView?.visibility = View.GONE
- binding?.footerTextView?.visibility = View.GONE
- binding?.openMarketButton?.visibility = View.GONE
-
- binding?.openMarketButton?.setOnClickListener {
- openMarket()
- }
setAdapter()
this.loadItems()
}
@@ -141,42 +156,55 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL
adapter = ItemRecyclerAdapter(context)
}
binding?.recyclerView?.adapter = adapter
- adapter?.onUseSpecialItem = { onSpecialItemSelected(it) }
- adapter?.onSellItem = {
- lifecycleScope.launchCatching {
- inventoryRepository.sellItem(it)
- }
+ }
+ adapter?.onUseSpecialItem = { onSpecialItemSelected(it) }
+ adapter?.onSellItem = {
+ lifecycleScope.launchCatching {
+ inventoryRepository.sellItem(it)
}
- adapter?.onQuestInvitation = {
- lifecycleScope.launchCatching {
- inventoryRepository.inviteToQuest(it)
- MainNavigationController.navigate(R.id.partyFragment)
- }
+ }
+ adapter?.onQuestInvitation = {
+ lifecycleScope.launchCatching {
+ inventoryRepository.inviteToQuest(it)
+ MainNavigationController.navigate(R.id.partyFragment)
}
- adapter?.onOpenMysteryItem = {
- lifecycleScope.launchCatching {
- val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching
- val activity = activity as? MainActivity
- if (activity != null) {
- val dialog = OpenedMysteryitemDialog(activity)
- dialog.isCelebratory = true
- dialog.setTitle(R.string.mystery_item_title)
- dialog.binding.iconView.loadImage("shop_${item.key}")
- dialog.binding.titleView.text = item.text
- dialog.binding.descriptionView.text = item.notes
- dialog.addButton(R.string.equip, true) { _, _ ->
- lifecycleScope.launchCatching {
- item.key?.let { mysteryItem -> inventoryRepository.equip("equipped", mysteryItem) }
- }
+ }
+ adapter?.onOpenMysteryItem = {
+ lifecycleScope.launchCatching {
+ val item = inventoryRepository.openMysteryItem(user) ?: return@launchCatching
+ val activity = activity as? MainActivity
+ if (activity != null) {
+ val dialog = OpenedMysteryitemDialog(activity)
+ dialog.isCelebratory = true
+ dialog.setTitle(R.string.mystery_item_title)
+ dialog.binding.iconView.loadImage("shop_${item.key}")
+ dialog.binding.titleView.text = item.text
+ dialog.binding.descriptionView.text = item.notes
+ dialog.addButton(R.string.equip, true) { _, _ ->
+ lifecycleScope.launchCatching {
+ item.key?.let { mysteryItem -> inventoryRepository.equip("equipped", mysteryItem) }
}
- dialog.addCloseButton()
- dialog.enqueue()
}
+ dialog.addCloseButton()
+ dialog.enqueue()
}
}
- adapter?.onStartHatching = { showHatchingDialog(it) }
- adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) }
- adapter?.onCreateNewParty = { createNewParty() }
+ }
+ adapter?.onStartHatching = { showHatchingDialog(it) }
+ adapter?.onHatchPet = { pet, egg -> hatchPet(pet, egg) }
+ adapter?.onCreateNewParty = { createNewParty() }
+ adapter?.itemType = itemType ?: ""
+ adapter?.itemText = (if (itemType == "hatchingPotions") context?.getString(R.string.potions) else itemTypeText) ?: ""
+ adapter?.onOpenShop = {
+ Analytics.sendEvent("Items CTA tap", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf(
+ "area" to "bottom",
+ "type" to (itemType ?: "")
+ ))
+ if (itemType == "quests") {
+ MainNavigationController.navigate(R.id.questShopFragment)
+ } else {
+ openMarket()
+ }
}
}
@@ -197,6 +225,7 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(ITEM_TYPE_KEY, this.itemType)
+ outState.putString(ITEM_TYPE_TEXT_KEY, this.itemTypeText)
}
override fun onRefresh() {
@@ -337,5 +366,6 @@ class ItemRecyclerFragment : BaseFragment(), SwipeRefreshL
companion object {
private const val ITEM_TYPE_KEY = "CLASS_TYPE_KEY"
+ private const val ITEM_TYPE_TEXT_KEY = "CLASS_TYPE_TEXT_KEY"
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/QuestShopFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/QuestShopFragment.kt
index 40bcc734cd..5ed26abba9 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/QuestShopFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/QuestShopFragment.kt
@@ -4,7 +4,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.models.shops.Shop
+import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
@@ -17,4 +19,14 @@ class QuestShopFragment : ShopFragment() {
shopIdentifier = Shop.QUEST_SHOP
return super.onCreateView(inflater, container, savedInstanceState)
}
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ lifecycleScope.launchCatching {
+ userRepository.getQuestAchievements().collect {
+ adapter?.completedQuests = it.map { it.questKey }
+ }
+ }
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt
index 4d0a4569f7..6bf1949652 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/shops/ShopFragment.kt
@@ -9,26 +9,31 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
-import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopCategory
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
+import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment
+import com.habitrpg.android.habitica.ui.fragments.purchases.SubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyText
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaProgressDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
+import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog
import com.habitrpg.android.habitica.ui.views.shops.PurchaseDialog
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.RecyclerViewState
@@ -112,22 +117,32 @@ open class ShopFragment : BaseMainFragment()
}
}
adapter?.onShowPurchaseDialog = { item, isPinned ->
- val dialog = PurchaseDialog(
- requireContext(),
- userRepository,
- inventoryRepository,
- item,
- mainActivity
- )
- dialog.shopIdentifier = shopIdentifier
- dialog.isPinned = isPinned
- dialog.onGearPurchased = {
- loadShopInventory()
- if (Shop.MARKET == shopIdentifier) {
- loadMarketGear()
+ if (item.key == "gem" && userViewModel.user.value?.isSubscribed != true) {
+ Analytics.sendEvent("View gems for gold CTA", EventCategory.BEHAVIOUR, HitType.EVENT)
+ val subscriptionBottomSheet = EventOutcomeSubscriptionBottomSheetFragment().apply {
+ eventType = EventOutcomeSubscriptionBottomSheetFragment.EVENT_GEMS_FOR_GOLD
}
+ activity?.let { activity ->
+ subscriptionBottomSheet.show(activity.supportFragmentManager, SubscriptionBottomSheetFragment.TAG)
+ }
+ } else {
+ val dialog = PurchaseDialog(
+ requireContext(),
+ userRepository,
+ inventoryRepository,
+ item,
+ mainActivity
+ )
+ dialog.shopIdentifier = shopIdentifier
+ dialog.isPinned = isPinned
+ dialog.onShopNeedsRefresh = {
+ loadShopInventory()
+ if (Shop.MARKET == shopIdentifier) {
+ loadMarketGear()
+ }
+ }
+ dialog.show()
}
- dialog.show()
}
adapter?.context = context
@@ -203,8 +218,6 @@ open class ShopFragment : BaseMainFragment()
view.post { setGridSpanCount(view.width) }
- context?.let { analyticsManager.logEvent("open_shop", bundleOf(Pair("shopIdentifier", shopIdentifier))) }
-
lifecycleScope.launchCatching {
inventoryRepository.getOwnedItems()
.collect { adapter?.setOwnedItems(it) }
@@ -214,6 +227,8 @@ open class ShopFragment : BaseMainFragment()
.map { rewards -> rewards.map { it.key } }
.collect { adapter?.setPinnedItemKeys(it) }
}
+
+ Analytics.sendNavigationEvent("$shopIdentifier screen")
}
open fun initializeCurrencyViews() {
@@ -231,6 +246,7 @@ open class ShopFragment : BaseMainFragment()
context?.let { context ->
if (user.gemCount <= 2) {
val dialog = mainActivity?.let { InsufficientGemsDialog(it, 3) }
+ Analytics.sendEvent("show insufficient gems modal", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("reason" to "class change"))
dialog?.show()
return@launch
}
@@ -251,7 +267,7 @@ open class ShopFragment : BaseMainFragment()
loadMarketGear()
}
}
- alert.addButton(R.string.dialog_go_back, false)
+ alert.addButton(R.string.close, false)
alert.show()
} else {
val alert = HabiticaAlertDialog(context)
@@ -269,7 +285,7 @@ open class ShopFragment : BaseMainFragment()
loadMarketGear()
}
}
- alert.addButton(R.string.dialog_go_back, false)
+ alert.addButton(R.string.close, false)
alert.show()
}
}
@@ -278,7 +294,9 @@ open class ShopFragment : BaseMainFragment()
override fun onResume() {
super.onResume()
- loadShopInventory()
+ if (shop == null) {
+ loadShopInventory()
+ }
}
private fun loadShopInventory() {
@@ -298,11 +316,13 @@ open class ShopFragment : BaseMainFragment()
val user = userViewModel.user.value
val specialCategory = ShopCategory()
specialCategory.text = getString(R.string.special)
- if (user?.isValid == true && user.purchased?.plan?.isActive == true) {
- val item = ShopItem.makeGemItem(context?.resources)
+ val item = ShopItem.makeGemItem(context?.resources)
+ if (user?.isSubscribed == true) {
item.limitedNumberLeft = user.purchased?.plan?.numberOfGemsLeft
- specialCategory.items.add(item)
+ } else {
+ item.limitedNumberLeft = -1
}
+ specialCategory.items.add(item)
specialCategory.items.add(ShopItem.makeFortifyItem(context?.resources))
shop1.categories.add(specialCategory)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt
index fc7f041154..03fc8a45ad 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/inventory/stable/PetDetailRecyclerFragment.kt
@@ -9,6 +9,8 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
+import com.habitrpg.android.habitica.extensions.observeOnce
+import com.habitrpg.android.habitica.helpers.ReviewManager
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
@@ -24,12 +26,15 @@ import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
+import com.habitrpg.shared.habitica.models.responses.FeedResponse
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
@AndroidEntryPoint
class PetDetailRecyclerFragment :
@@ -51,6 +56,9 @@ class PetDetailRecyclerFragment :
private var animalColor: String? = null
internal var layoutManager: androidx.recyclerview.widget.GridLayoutManager? = null
+ @Inject
+ lateinit var reviewManager: ReviewManager
+
override var binding: FragmentRefreshRecyclerviewBinding? = null
override fun createBinding(
@@ -124,6 +132,14 @@ class PetDetailRecyclerFragment :
lifecycleScope.launchCatching {
val items = inventoryRepository.equip("pet", it)
adapter.currentPet = items?.currentPet
+
+ userViewModel.user.observeOnce(viewLifecycleOwner) { user ->
+ val parentActivity = mainActivity
+ val totalCheckIns = user?.loginIncentives
+ if (totalCheckIns != null && parentActivity != null) {
+ reviewManager.requestReview(parentActivity, totalCheckIns)
+ }
+ }
}
}
userViewModel.user.observe(viewLifecycleOwner) { adapter.currentPet = it?.currentPet }
@@ -218,27 +234,29 @@ class PetDetailRecyclerFragment :
}
}
- private fun showFeedingDialog(pet: Pet, food: Food?) {
+ private suspend fun showFeedingDialog(pet: Pet, food: Food?): FeedResponse? {
if (food != null) {
- val context = mainActivity ?: context ?: return
- lifecycleScope.launchCatching {
- feedPetUseCase.callInteractor(
- FeedPetUseCase.RequestValues(
- pet,
- food,
- context
- )
+ val context = mainActivity ?: context ?: return null
+ return feedPetUseCase.callInteractor(
+ FeedPetUseCase.RequestValues(
+ pet,
+ food,
+ context
)
+ )
+ }
+ return suspendCoroutine { cont ->
+ val fragment = ItemDialogFragment()
+ fragment.feedingPet = pet
+ fragment.isFeeding = true
+ fragment.isHatching = false
+ fragment.itemType = "food"
+ fragment.itemTypeText = getString(R.string.food)
+ fragment.onFeedResult = {
+ cont.resume(it)
}
- return
+ parentFragmentManager.let { fragment.show(it, "feedDialog") }
}
- val fragment = ItemDialogFragment()
- fragment.feedingPet = pet
- fragment.isFeeding = true
- fragment.isHatching = false
- fragment.itemType = "food"
- fragment.itemTypeText = getString(R.string.food)
- parentFragmentManager.let { fragment.show(it, "feedDialog") }
}
companion object {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt
index 6236703bf4..5c4bffedd0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/AccountPreferenceFragment.kt
@@ -23,7 +23,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.addCloseButton
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.FixCharacterValuesActivity
import com.habitrpg.android.habitica.ui.fragments.preferences.HabiticaAccountDialog.AccountUpdateConfirmed
@@ -418,7 +418,7 @@ class AccountPreferenceFragment :
}
}
- private val regex = "[^a-z0-9_-]".toRegex()
+ private val regex = "[^a-zA-Z0-9_-]".toRegex()
private fun showLoginNameDialog() {
showSingleEntryDialog(user?.username, getString(R.string.username), {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt
index 35fdc780e0..40688869ae 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/EmailNotificationsPreferencesFragment.kt
@@ -49,7 +49,7 @@ class EmailNotificationsPreferencesFragment : BasePreferencesFragment(), SharedP
preference?.isChecked = isChecked == true
}
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
if (isSettingUser) {
return
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
index c8d95d3913..3f1014eb13 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
@@ -20,7 +20,10 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.extensions.addCancelButton
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
@@ -176,6 +179,7 @@ class PreferencesFragment :
} else {
activity?.let { activity ->
val dialog = InsufficientGemsDialog(activity, 3)
+ Analytics.sendEvent("show insufficient gems modal", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("reason" to "class change"))
dialog.show()
}
}
@@ -416,6 +420,13 @@ class PreferencesFragment :
}
)
+ val themePreference = findPreference("theme_name") as? ListPreference
+ if (themePreference?.value == "Default") themePreference.value = "purple"
+
+ val themeModePreference = findPreference("theme_mode") as? ListPreference
+ if (themeModePreference?.value == "Follow System") themeModePreference.value = "system"
+
+
if (10 <= (user?.stats?.lvl ?: 0)) {
if (user?.flags?.classSelected == true) {
if (user.preferences?.disableClasses == true) {
@@ -460,6 +471,9 @@ class PreferencesFragment :
resources.getStringArray(R.array.launch_screen_values).dropLast(1).toTypedArray()
}
+ val launchScreenPreference = findPreference("launch_screen")
+ if (launchScreenPreference?.value == "habits") launchScreenPreference.value = "/user/tasks/habits"
+
val disablePMsPreference = findPreference("disablePMs") as? CheckBoxPreference
val inbox = user?.inbox
disablePMsPreference?.isChecked = inbox?.optOut ?: true
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt
index 8ab9c66b4f..b7f24651bb 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PushNotificationsPreferencesFragment.kt
@@ -40,7 +40,6 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr
updatePreference("preference_push_party_activity", user?.preferences?.pushNotifications?.partyActivity)
updatePreference("preference_push_party_mention", user?.preferences?.pushNotifications?.mentionParty)
updatePreference("preference_push_joined_guild_mention", user?.preferences?.pushNotifications?.mentionJoinedGuild)
- updatePreference("preference_push_unjoined_guild_mention", user?.preferences?.pushNotifications?.mentionUnjoinedGuild)
isSettingUser = false
isInitialSet = false
}
@@ -50,7 +49,7 @@ class PushNotificationsPreferencesFragment : BasePreferencesFragment(), SharedPr
preference?.isChecked = isChecked == true
}
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
+ override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
if (isSettingUser) {
return
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/EventOutcomeSubscriptionBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/EventOutcomeSubscriptionBottomSheetFragment.kt
new file mode 100644
index 0000000000..d65faa732a
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/EventOutcomeSubscriptionBottomSheetFragment.kt
@@ -0,0 +1,54 @@
+package com.habitrpg.android.habitica.ui.fragments.purchases
+
+import android.os.Bundle
+import android.view.View
+import com.habitrpg.android.habitica.R
+
+class EventOutcomeSubscriptionBottomSheetFragment : SubscriptionBottomSheetFragment() {
+
+ var eventType: String = ""
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ when (eventType) {
+ EVENT_ARMOIRE_OPENED -> setArmoireEventSubscriptionViews()
+ EVENT_DEATH_SCREEN -> setDeathScreenEventSubscriptionViews()
+ EVENT_GEMS_FOR_GOLD -> setGemsForGoldEventSubscriptionViews()
+ EVENT_HOURGLASS_SHOP_OPENED -> setHourglassShopEventSubscriptionViews()
+ }
+
+ }
+
+ private fun setArmoireEventSubscriptionViews() {
+ binding.subscriberBenefitBanner.visibility = View.GONE
+ binding.subscribeBenefits.text = getString(R.string.subscribe_second_armoire_open_text)
+ binding.subscriberBenefits.hideArmoireBenefit()
+ }
+
+ private fun setDeathScreenEventSubscriptionViews() {
+ binding.subscriberBenefitBanner.visibility = View.GONE
+ binding.subscribeBenefits.text = getString(R.string.subscribe_second_chance_incentive_text)
+ binding.subscriberBenefits.hideDeathBenefit()
+ }
+
+ private fun setGemsForGoldEventSubscriptionViews() {
+ binding.subscribeBenefits.text = getString(R.string.subscribe_gems_for_gold_incentive_text)
+ binding.subscriberBenefits.hideGemsForGoldBenefit()
+ binding.subscription3month.visibility = View.GONE
+ }
+
+ private fun setHourglassShopEventSubscriptionViews() {
+ binding.subscriberBenefitBanner.visibility = View.GONE
+ binding.subscribeBenefits.text = getString(R.string.subscribe_hourglass_incentive_text)
+ binding.subscriberBenefits.hideMysticHourglassBenefit()
+ binding.subscription1month.visibility = View.GONE
+ }
+
+ companion object {
+ const val TAG = "EventOutcomeSubscriptionBottomSheet"
+ const val EVENT_ARMOIRE_OPENED = "armoire_opened"
+ const val EVENT_DEATH_SCREEN = "death_screen"
+ const val EVENT_GEMS_FOR_GOLD = "gems_for_gold"
+ const val EVENT_HOURGLASS_SHOP_OPENED = "hourglass_shop_opened"
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt
index de5e84fe53..1c4a81e4d3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/GemsPurchaseFragment.kt
@@ -18,7 +18,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
@@ -34,12 +34,12 @@ import com.habitrpg.android.habitica.ui.views.promo.BirthdayBanner
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
+import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
-import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class GemsPurchaseFragment : BaseFragment() {
@@ -112,7 +112,7 @@ class GemsPurchaseFragment : BaseFragment() {
binding?.promoComposeView?.isVisible = true
}
- AmplitudeManager.sendNavigationEvent("gem screen")
+ Analytics.sendNavigationEvent("gem screen")
}
override fun onResume() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionBottomSheetFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionBottomSheetFragment.kt
new file mode 100644
index 0000000000..50cf0b65ad
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionBottomSheetFragment.kt
@@ -0,0 +1,200 @@
+package com.habitrpg.android.habitica.ui.fragments.purchases
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
+import com.android.billingclient.api.ProductDetails
+import com.google.android.material.bottomsheet.BottomSheetBehavior
+import com.google.android.material.bottomsheet.BottomSheetDialog
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.data.InventoryRepository
+import com.habitrpg.android.habitica.data.UserRepository
+import com.habitrpg.android.habitica.databinding.FragmentBottomsheetSubscriptionBinding
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.PurchaseHandler
+import com.habitrpg.android.habitica.helpers.PurchaseTypes
+import com.habitrpg.android.habitica.models.user.User
+import com.habitrpg.android.habitica.ui.views.subscriptions.SubscriptionOptionView
+import com.habitrpg.common.habitica.extensions.loadImage
+import com.habitrpg.common.habitica.helpers.ExceptionHandler
+import com.habitrpg.common.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.launchCatching
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import javax.inject.Inject
+
+@AndroidEntryPoint
+open class SubscriptionBottomSheetFragment : BottomSheetDialogFragment() {
+
+ private var _binding: FragmentBottomsheetSubscriptionBinding? = null
+ val binding get() = _binding!!
+
+ @Inject
+ lateinit var userRepository: UserRepository
+
+ @Inject
+ lateinit var appConfigManager: AppConfigManager
+
+ @Inject
+ lateinit var inventoryRepository: InventoryRepository
+
+ @Inject
+ lateinit var purchaseHandler: PurchaseHandler
+
+ private var selectedSubscriptionSku: ProductDetails? = null
+ private var skus: List = emptyList()
+
+ private var user: User? = null
+ private var hasLoadedSubscriptionOptions: Boolean = false
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ _binding = FragmentBottomsheetSubscriptionBinding.inflate(layoutInflater)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.subscriptionOptions.visibility = View.GONE
+ binding.seeMoreOptions.setOnClickListener {
+ dismiss()
+ MainNavigationController.navigate(R.id.subscriptionPurchaseActivity)
+ }
+ binding.subscribeButton.setOnClickListener { purchaseSubscription() }
+
+ lifecycleScope.launchCatching {
+ userRepository.getUser().collect { user ->
+ user?.let { setUser(it) }
+ }
+ }
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
+ bottomSheetDialog.setOnShowListener { dialog: DialogInterface ->
+ val notificationDialog = dialog as BottomSheetDialog
+ notificationDialog.behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
+ notificationDialog.behavior.isDraggable = true
+ }
+ return bottomSheetDialog
+ }
+
+ override fun onResume() {
+ super.onResume()
+ lifecycleScope.launchCatching {
+ purchaseHandler.queryPurchases()
+ }
+ refresh()
+ loadInventory()
+ }
+
+ private fun refresh() {
+ lifecycleScope.launch(ExceptionHandler.coroutine()) {
+ userRepository.retrieveUser(false, true)
+ }
+ }
+
+ private fun loadInventory() {
+ CoroutineScope(Dispatchers.IO).launchCatching {
+ val subscriptions = purchaseHandler.getAllSubscriptionProducts()
+ skus = subscriptions
+ withContext(Dispatchers.Main) {
+ for (sku in subscriptions) {
+ updateButtonLabel(sku, sku.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.formattedPrice ?: "")
+ }
+ subscriptions.minByOrNull { it.subscriptionOfferDetails?.firstOrNull()?.pricingPhases?.pricingPhaseList?.firstOrNull()?.priceAmountMicros ?: 0 }?.let { selectSubscription(it) }
+ hasLoadedSubscriptionOptions = true
+ updateSubscriptionInfo()
+ }
+ }
+ }
+
+ private fun updateButtonLabel(sku: ProductDetails, price: String) {
+ val matchingView = buttonForSku(sku)
+ if (matchingView != null) {
+ matchingView.setPriceText(price)
+ matchingView.sku = sku.productId
+ matchingView.setOnPurchaseClickListener {
+ selectSubscription(sku)
+ }
+ }
+ }
+
+ private fun selectSubscription(sku: ProductDetails) {
+ if (this.selectedSubscriptionSku != null) {
+ val oldButton = buttonForSku(this.selectedSubscriptionSku)
+ oldButton?.setIsSelected(false)
+ }
+ this.selectedSubscriptionSku = sku
+ val subscriptionOptionButton = buttonForSku(this.selectedSubscriptionSku)
+ subscriptionOptionButton?.setIsSelected(true)
+ binding.subscribeButton.isEnabled = true
+ }
+
+ private fun buttonForSku(sku: ProductDetails?): SubscriptionOptionView? {
+ return buttonForSku(sku?.productId)
+ }
+
+ private fun buttonForSku(sku: String?): SubscriptionOptionView? {
+ return when (sku) {
+ PurchaseTypes.Subscription1Month -> binding.subscription1month
+ PurchaseTypes.Subscription3Month -> binding.subscription3month
+ PurchaseTypes.Subscription12Month -> binding.subscription12month
+ else -> null
+ }
+ }
+
+ private fun purchaseSubscription() {
+ selectedSubscriptionSku?.let { sku ->
+ activity?.let {
+ purchaseHandler.purchase(it, sku)
+ dismiss()
+ }
+ }
+ }
+
+ fun setUser(newUser: User) {
+ user = newUser
+ this.updateSubscriptionInfo()
+ checkIfNeedsCancellation()
+ }
+
+ private fun updateSubscriptionInfo() {
+ if (hasLoadedSubscriptionOptions) {
+ binding.subscriptionOptions.visibility = View.VISIBLE
+ binding.loadingIndicator.visibility = View.GONE
+ }
+ if (user != null) {
+ binding.loadingIndicator.visibility = View.GONE
+ }
+ }
+
+ private fun checkIfNeedsCancellation() {
+ CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
+ val newestSubscription = purchaseHandler.checkForSubscription()
+ if (user?.purchased?.plan?.paymentMethod == "Google" &&
+ user?.purchased?.plan?.isActive == true &&
+ user?.purchased?.plan?.dateTerminated == null &&
+ (newestSubscription?.isAutoRenewing != true)
+ ) {
+ lifecycleScope.launch(ExceptionHandler.coroutine()) {
+ purchaseHandler.cancelSubscription()
+ }
+ }
+ }
+ }
+
+ companion object {
+ const val TAG = "SubscriptionBottomSheet"
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt
index b7b1ab7ff7..d7e513126f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/purchases/SubscriptionFragment.kt
@@ -20,7 +20,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentSubscriptionBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
@@ -37,12 +37,12 @@ import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
+import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
-import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SubscriptionFragment : BaseFragment() {
@@ -120,21 +120,7 @@ class SubscriptionFragment : BaseFragment() {
binding?.refreshLayout?.setOnRefreshListener { refresh() }
- lifecycleScope.launchCatching {
- inventoryRepository.getLatestMysteryItem().collect {
- binding?.subBenefitsMysteryItemIcon?.loadImage(
- "shop_set_mystery_${
- it.key?.split(
- "_"
- )?.last()
- }"
- )
- binding?.subBenefitsMysteryItemText?.text =
- context?.getString(R.string.subscribe_listitem3_description_new, it.text)
- }
- }
-
- AmplitudeManager.sendNavigationEvent("subscription screen")
+ Analytics.sendNavigationEvent("subscription screen")
}
override fun onResume() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
index 26fda7041b..1c26c0063d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatFragment.kt
@@ -19,12 +19,13 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentChatBinding
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.social.ChatRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
+import com.habitrpg.android.habitica.ui.fragments.ReportBottomSheetFragment
import com.habitrpg.android.habitica.ui.helpers.AutocompleteAdapter
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel
@@ -78,7 +79,7 @@ open class ChatFragment : BaseFragment() {
chatAdapter?.let { adapter ->
adapter.onOpenProfile = { userId -> FullProfileActivity.open(userId) }
adapter.onDeleteMessage = { this.showDeleteConfirmationDialog(it) }
- adapter.onFlagMessage = { this.showFlagConfirmationDialog(it) }
+ adapter.onFlagMessage = { this.showFlagMessageBottomSheet(it) }
adapter.onReply = { setReplyTo(it) }
adapter.onCopyMessage = { this.copyMessageToClipboard(it) }
adapter.onMessageLike = { viewModel.likeMessage(it) }
@@ -181,14 +182,18 @@ open class ChatFragment : BaseFragment() {
}
}
- private fun showFlagConfirmationDialog(chatMessage: ChatMessage) {
- val directions = MainNavDirections.actionGlobalReportMessageActivity(
- chatMessage.text ?: "",
- chatMessage.user ?: "",
- chatMessage.id,
- chatMessage.groupId
+ private fun showFlagMessageBottomSheet(chatMessage : ChatMessage) {
+ val reportBottomSheetFragment = ReportBottomSheetFragment.newInstance(
+ reportType = ReportBottomSheetFragment.REPORT_TYPE_MESSAGE,
+ profileName = chatMessage.username ?: "",
+ messageId = chatMessage.id,
+ messageText = chatMessage.text ?: "",
+ groupId = chatMessage.groupId ?: "",
+ userIdBeingReported = chatMessage.userID ?: "",
+ sourceView = this::class.simpleName ?: ""
)
- MainNavigationController.navigate(directions)
+
+ reportBottomSheetFragment.show(childFragmentManager, ReportBottomSheetFragment.TAG)
}
private fun showDeleteConfirmationDialog(chatMessage: ChatMessage) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt
index 15c395fc04..c5f7a405ff 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/InboxMessageListFragment.kt
@@ -22,12 +22,13 @@ import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentInboxMessageListBinding
import com.habitrpg.android.habitica.extensions.addOkButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.social.InboxAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
+import com.habitrpg.android.habitica.ui.fragments.ReportBottomSheetFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.InboxViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
@@ -99,7 +100,7 @@ class InboxMessageListFragment : BaseMainFragment(), androidx
binding.progressCircular.visibility = View.VISIBLE
val username = binding.uuidEditText.text?.toString() ?: ""
lifecycleScope.launch(ExceptionHandler.coroutine()) {
- val member = socialRepository.retrieveMemberWithUsername(username, false)
- if (member != null) {
- alert.dismiss()
- openInboxMessages("", username)
- binding.progressCircular.visibility = View.GONE
- } else {
- binding.errorTextView.visibility = View.VISIBLE
- binding.progressCircular.visibility = View.GONE
+ var member: Member? = null
+ try {
+ member = socialRepository.retrieveMember(username, false)
+ } finally {
+ if (member != null) {
+ alert.dismiss()
+ openInboxMessages("", username)
+ binding.progressCircular.visibility = View.GONE
+ } else {
+ binding.errorTextView.visibility = View.VISIBLE
+ binding.progressCircular.visibility = View.GONE
+ }
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt
deleted file mode 100644
index bd8f6a4729..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernDetailFragment.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.social
-
-import android.content.Context
-import android.graphics.PorterDuff
-import android.os.Bundle
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.TextView
-import androidx.appcompat.app.AlertDialog
-import androidx.core.content.ContextCompat
-import androidx.lifecycle.lifecycleScope
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.InventoryRepository
-import com.habitrpg.android.habitica.data.SocialRepository
-import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.databinding.FragmentTavernDetailBinding
-import com.habitrpg.android.habitica.extensions.setTintWith
-import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
-import com.habitrpg.android.habitica.models.inventory.QuestContent
-import com.habitrpg.android.habitica.models.social.Group
-import com.habitrpg.android.habitica.models.user.User
-import com.habitrpg.android.habitica.ui.fragments.BaseFragment
-import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
-import com.habitrpg.android.habitica.ui.views.UsernameLabel
-import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
-import com.habitrpg.common.habitica.helpers.ExceptionHandler
-import com.habitrpg.common.habitica.models.PlayerTier
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class TavernDetailFragment : BaseFragment() {
-
- @Inject
- lateinit var userRepository: UserRepository
-
- @Inject
- lateinit var socialRepository: SocialRepository
-
- @Inject
- lateinit var inventoryRepository: InventoryRepository
-
- @Inject
- lateinit var userViewModel: MainUserViewModel
-
- @Inject
- lateinit var configManager: AppConfigManager
-
- private var shopSpriteSuffix = ""
-
- override var binding: FragmentTavernDetailBinding? = null
-
- override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTavernDetailBinding {
- return FragmentTavernDetailBinding.inflate(inflater, container, false)
- }
-
- private var user: User? = null
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- shopSpriteSuffix = configManager.shopSpriteSuffix()
-
- userViewModel.user.observe(viewLifecycleOwner) {
- user = it
- updatePausedState()
- }
-
- binding?.shopHeader?.descriptionView?.setText(R.string.tavern_description)
- binding?.shopHeader?.namePlate?.setText(R.string.tavern_owner)
-
- binding?.shopHeader?.npcBannerView?.shopSpriteSuffix = configManager.shopSpriteSuffix()
- binding?.shopHeader?.npcBannerView?.identifier = "tavern"
-
- addPlayerTiers()
- bindButtons()
-
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- socialRepository.getGroup(Group.TAVERN_ID)
- .onEach { if (it?.hasActiveQuest == false) binding?.worldBossSection?.visibility = View.GONE }
- .filter { it != null && it.hasActiveQuest }
- .onEach {
- binding?.questProgressView?.progress = it?.quest
- binding?.shopHeader?.descriptionView?.setText(R.string.tavern_description_world_boss)
- val filtered = it?.quest?.rageStrikes?.filter { strike -> strike.key == "tavern" }
- if ((filtered?.size ?: 0) > 0 && filtered?.get(0)?.wasHit == true) {
- val key = it.quest?.key
- if (key != null) {
- shopSpriteSuffix = key
- }
- }
- }
- .map { inventoryRepository.getQuestContent(it?.quest?.key ?: "").firstOrNull() }
- .collect {
- binding?.questProgressView?.quest = it
- binding?.worldBossSection?.visibility = View.VISIBLE
- }
- }
-
- lifecycleScope.launch(ExceptionHandler.coroutine()) { socialRepository.retrieveGroup(Group.TAVERN_ID) }
-
- user?.let { binding?.questProgressView?.configure(it) }
- }
-
- override fun onDestroy() {
- userRepository.close()
- socialRepository.close()
- inventoryRepository.close()
- super.onDestroy()
- }
-
- private fun bindButtons() {
- binding?.innButton?.setOnClickListener {
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- user?.let { user -> userRepository.sleep(user) }
- }
- }
- binding?.guidelinesButton?.setOnClickListener {
- MainNavigationController.navigate(R.id.guidelinesActivity)
- }
- binding?.faqButton?.setOnClickListener {
- MainNavigationController.navigate(R.id.FAQOverviewFragment)
- }
- binding?.reportButton?.setOnClickListener {
- MainNavigationController.navigate(R.id.aboutFragment)
- }
-
- binding?.worldBossSection?.infoIconView?.setOnClickListener {
- val context = this.context
- val quest = binding?.questProgressView?.quest
- if (context != null && quest != null) {
- showWorldBossInfoDialog(context, quest)
- }
- }
- }
-
- private fun updatePausedState() {
- if (binding?.innButton == null) {
- return
- }
- if (user?.preferences?.sleep == true) {
- binding?.innButton?.setText(R.string.tavern_inn_checkOut)
- } else {
- binding?.innButton?.setText(R.string.tavern_inn_rest)
- }
- }
-
- private fun addPlayerTiers() {
- for (tier in PlayerTier.getTiers()) {
- context?.let {
- val container = FrameLayout(it)
- container.background = ContextCompat.getDrawable(it, R.drawable.layout_rounded_bg_window)
- val label = UsernameLabel(it, null)
- label.tier = tier.id
- label.username = tier.title
- val params = FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.WRAP_CONTENT,
- FrameLayout.LayoutParams.WRAP_CONTENT,
- Gravity.CENTER
- )
- container.addView(label, params)
- binding?.playerTiersView?.addView(container)
- val padding = context?.resources?.getDimension(R.dimen.spacing_medium)?.toInt() ?: 0
- container.setPadding(0, padding, 0, padding)
- }
- }
- (binding?.playerTiersView?.parent as? ViewGroup)?.invalidate()
- }
-
- companion object {
-
- fun showWorldBossInfoDialog(context: Context, quest: QuestContent) {
- val alert = HabiticaAlertDialog(context)
- val bossName = quest.boss?.name ?: ""
- alert.setTitle(R.string.world_boss_description_title)
- // alert.setSubtitle(context.getString(R.string.world_boss_description_subtitle, bossName))
- alert.setAdditionalContentView(R.layout.world_boss_description_view)
-
- val descriptionView = alert.getContentView()
- val promptView: TextView? = descriptionView?.findViewById(R.id.worldBossActionPromptView)
- promptView?.text = context.getString(R.string.world_boss_action_prompt, bossName)
- promptView?.setTextColor(quest.colors?.lightColor ?: 0)
- val background = ContextCompat.getDrawable(context, R.drawable.rounded_border)
- background?.setTintWith(quest.colors?.extraLightColor ?: 0, PorterDuff.Mode.MULTIPLY)
- promptView?.background = background
-
- alert.setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.close)) { dialog, _ ->
- dialog.dismiss()
- }
- alert.show()
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt
deleted file mode 100644
index 61544ab98e..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/TavernFragment.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.social
-
-import android.os.Bundle
-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 androidx.fragment.app.Fragment
-import androidx.fragment.app.viewModels
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import com.google.android.material.tabs.TabLayoutMediator
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.SocialRepository
-import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
-import com.habitrpg.android.habitica.models.social.Group
-import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
-import com.habitrpg.android.habitica.ui.viewmodels.GroupViewModel
-import com.habitrpg.android.habitica.ui.viewmodels.GroupViewType
-import dagger.hilt.android.AndroidEntryPoint
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class TavernFragment : BaseMainFragment() {
-
- @Inject
- lateinit var socialRepository: SocialRepository
-
- override var binding: FragmentViewpagerBinding? = null
-
- override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
- return FragmentViewpagerBinding.inflate(inflater, container, false)
- }
-
- internal val viewModel: GroupViewModel by viewModels()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- this.usesTabLayout = true
- this.hidesToolbar = true
-
- return super.onCreateView(inflater, container, savedInstanceState)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- viewModel.groupViewType = GroupViewType.GUILD
- viewModel.setGroupID(Group.TAVERN_ID)
-
- setViewPagerAdapter()
- binding?.viewPager?.currentItem = 0
- }
-
- override fun onDestroy() {
- socialRepository.close()
- super.onDestroy()
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- inflater.inflate(R.menu.menu_tavern, menu)
- super.onCreateOptionsMenu(menu, inflater)
- }
-
- @Suppress("ReturnCount")
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.menu_guild_refresh -> {
- viewModel.retrieveGroup { }
- return true
- }
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun setViewPagerAdapter() {
- val fragmentManager = childFragmentManager
- binding?.viewPager?.adapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
- override fun createFragment(position: Int): Fragment {
- return when (position) {
- 0 -> {
- TavernDetailFragment()
- }
- 1 -> {
- ChatFragment()
- }
- else -> Fragment()
- }
- }
-
- override fun getItemCount(): Int {
- return if (viewModel.getGroupData().value?.quest?.active == true) {
- 3
- } else {
- 2
- }
- }
- }
- tabLayout?.let {
- binding?.viewPager?.let { it1 ->
- TabLayoutMediator(it, it1) { tab, position ->
- tab.text = when (position) {
- 0 -> context?.getString(R.string.inn)
- 1 -> context?.getString(R.string.chat)
- 2 -> context?.getString(R.string.world_quest)
- else -> ""
- }
- }.attach()
- }
- }
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt
index 233d1d4ef4..c05f706868 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeDetailFragment.kt
@@ -19,7 +19,7 @@ import com.habitrpg.android.habitica.databinding.DialogChallengeDetailTaskGroupB
import com.habitrpg.android.habitica.databinding.FragmentChallengeDetailBinding
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.inflate
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt
index 48fdfa1228..9ba0520f21 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/challenges/ChallengeListFragment.kt
@@ -11,7 +11,7 @@ import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter
@@ -117,6 +117,8 @@ class ChallengeListFragment :
}
}
})
+
+ retrieveChallengesPage(true)
}
private fun openDetailFragment(challengeID: String) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt
index 55c5f03d29..7ff3cf72fd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildDetailFragment.kt
@@ -16,7 +16,7 @@ import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentGuildDetailBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.social.Group
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt
deleted file mode 100644
index 795e15279f..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildListFragment.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.social.guilds
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.appcompat.widget.SearchView
-import androidx.lifecycle.lifecycleScope
-import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.SocialRepository
-import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBinding
-import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter
-import com.habitrpg.android.habitica.ui.fragments.BaseFragment
-import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
-import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
-import com.habitrpg.common.habitica.helpers.EmptyItem
-import com.habitrpg.common.habitica.helpers.ExceptionHandler
-import com.habitrpg.common.habitica.helpers.launchCatching
-import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class GuildListFragment :
- BaseFragment(),
- SearchView.OnQueryTextListener,
- SearchView.OnCloseListener,
- SwipeRefreshLayout.OnRefreshListener {
-
- @Inject
- lateinit var socialRepository: SocialRepository
-
- override var binding: FragmentRefreshRecyclerviewBinding? = null
- var onlyShowUsersGuilds: Boolean = false
-
- override fun createBinding(
- inflater: LayoutInflater,
- container: ViewGroup?
- ): FragmentRefreshRecyclerviewBinding {
- return FragmentRefreshRecyclerviewBinding.inflate(inflater, container, false)
- }
-
- private var viewAdapter = GuildListAdapter()
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- binding?.recyclerView?.layoutManager =
- androidx.recyclerview.widget.LinearLayoutManager(activity)
- viewAdapter.socialRepository = socialRepository
- binding?.recyclerView?.adapter = viewAdapter
- binding?.recyclerView?.itemAnimator = SafeDefaultItemAnimator()
- binding?.recyclerView?.emptyItem = EmptyItem(
- getString(R.string.empty_guilds_list),
- getString(R.string.empty_discover_description)
- )
-
- binding?.refreshLayout?.setOnRefreshListener(this)
-
- viewAdapter.onlyShowUsersGuilds = onlyShowUsersGuilds
- if (onlyShowUsersGuilds) {
- lifecycleScope.launch(ExceptionHandler.coroutine()) {
- socialRepository.getUserGroups("guild")
- .collect {
- viewAdapter.setUnfilteredData(it)
- }
- }
- } else {
- lifecycleScope.launchCatching {
- socialRepository.getPublicGuilds().collect { groups ->
- this@GuildListFragment.viewAdapter.setUnfilteredData(groups)
- }
- }
- }
- this.fetchGuilds()
- }
-
- override fun onDestroy() {
- socialRepository.close()
- super.onDestroy()
- }
-
- internal fun fetchGuilds() {
- lifecycleScope.launchCatching {
- socialRepository.retrieveGroups(if (onlyShowUsersGuilds) "guilds" else "publicGuilds")
- binding?.refreshLayout?.isRefreshing = false
- }
- }
-
- override fun onQueryTextSubmit(s: String?): Boolean {
- viewAdapter.filter.filter(s)
- activity?.let {
- KeyboardUtil.dismissKeyboard(it)
- }
- return true
- }
-
- override fun onQueryTextChange(s: String?): Boolean {
- viewAdapter.filter.filter(s)
- return true
- }
-
- override fun onRefresh() {
- fetchGuilds()
- }
-
- override fun onClose(): Boolean {
- viewAdapter.filter.filter("")
- return false
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildOverviewFragment.kt
deleted file mode 100644
index 0c451f7a6c..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildOverviewFragment.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-package com.habitrpg.android.habitica.ui.fragments.social.guilds
-
-import android.content.Intent
-import android.content.pm.PackageManager
-import android.os.Bundle
-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 androidx.appcompat.widget.SearchView
-import androidx.core.content.ContextCompat
-import androidx.core.net.toUri
-import androidx.fragment.app.Fragment
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import com.google.android.material.tabs.TabLayoutMediator
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.data.SocialRepository
-import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
-import com.habitrpg.android.habitica.extensions.addCloseButton
-import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
-import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
-import dagger.hilt.android.AndroidEntryPoint
-import javax.inject.Inject
-
-@AndroidEntryPoint
-class GuildOverviewFragment : BaseMainFragment(), SearchView.OnQueryTextListener {
-
- @Inject
- internal lateinit var socialRepository: SocialRepository
-
- override var binding: FragmentViewpagerBinding? = null
-
- override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentViewpagerBinding {
- return FragmentViewpagerBinding.inflate(inflater, container, false)
- }
-
- private var statePagerAdapter: FragmentStateAdapter? = null
- private var userGuildsFragment: GuildListFragment? = GuildListFragment()
- private var publicGuildsFragment: GuildListFragment? = GuildListFragment()
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- this.usesTabLayout = true
- this.hidesToolbar = true
- return super.onCreateView(inflater, container, savedInstanceState)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- setViewPagerAdapter()
- }
-
- override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
- super.onCreateOptionsMenu(menu, inflater)
- inflater.inflate(R.menu.menu_public_guild, menu)
-
- val searchItem = menu.findItem(R.id.action_search)
- val guildSearchView = searchItem?.actionView as? SearchView
- val theTextArea = guildSearchView?.findViewById(R.id.search_src_text)
- context?.let { theTextArea?.setHintTextColor(ContextCompat.getColor(it, R.color.white)) }
- guildSearchView?.queryHint = getString(R.string.guild_search_hint)
- guildSearchView?.setOnQueryTextListener(this)
- guildSearchView?.setOnCloseListener {
- getActiveFragment()?.onClose() ?: true
- }
- }
-
- @Suppress("ReturnCount")
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.menu_create_item -> {
- showCreationDialog()
- return true
- }
- R.id.action_reload -> {
- getActiveFragment()?.fetchGuilds()
- return true
- }
- }
-
- return super.onOptionsItemSelected(item)
- }
-
- private fun showCreationDialog() {
- val context = context ?: return
- val dialog = HabiticaAlertDialog(context)
- dialog.setTitle(R.string.create_guild)
- dialog.setMessage(R.string.create_guild_description)
- dialog.addButton(R.string.open_website, isPrimary = true, isDestructive = false) { _, _ ->
- val uriUrl = "https://habitica.com/groups/myGuilds".toUri()
- val launchBrowser = Intent(Intent.ACTION_VIEW, uriUrl)
- val l = context.packageManager.queryIntentActivities(launchBrowser, PackageManager.MATCH_DEFAULT_ONLY)
- val notHabitica = l.firstOrNull() { !it.activityInfo.processName.contains("habitica") }
- launchBrowser.setPackage(notHabitica?.activityInfo?.processName)
- startActivity(launchBrowser)
- }
- dialog.addCloseButton()
- dialog.show()
- }
-
- private fun getActiveFragment(): GuildListFragment? {
- return if (binding?.viewPager?.currentItem == 0) {
- userGuildsFragment
- } else {
- publicGuildsFragment
- }
- }
-
- private fun setViewPagerAdapter() {
- val fragmentManager = childFragmentManager
-
- statePagerAdapter = object : FragmentStateAdapter(fragmentManager, lifecycle) {
-
- override fun createFragment(position: Int): Fragment {
- val fragment = GuildListFragment()
- fragment.onlyShowUsersGuilds = position == 0
- if (position == 0) {
- userGuildsFragment = fragment
- } else {
- publicGuildsFragment = fragment
- }
- return fragment
- }
-
- override fun getItemCount(): Int {
- return 2
- }
- }
- binding?.viewPager?.adapter = statePagerAdapter
- tabLayout?.let {
- binding?.viewPager?.let { it1 ->
- TabLayoutMediator(it, it1) { tab, position ->
- tab.text = when (position) {
- 0 -> getString(R.string.my_guilds)
- 1 -> getString(R.string.discover)
- else -> ""
- }
- }.attach()
- }
- }
- statePagerAdapter?.notifyDataSetChanged()
- }
-
- override fun onQueryTextSubmit(query: String?): Boolean {
- return getActiveFragment()?.onQueryTextSubmit(query) ?: false
- }
-
- override fun onQueryTextChange(newText: String?): Boolean {
- return getActiveFragment()?.onQueryTextChange(newText) ?: false
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt
index 06d0805823..aa0d762442 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/NoPartyFragmentFragment.kt
@@ -18,7 +18,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.FragmentNoPartyBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
index 80ba74cc01..b8621fef88 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyDetailFragment.kt
@@ -22,7 +22,7 @@ import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentPartyDetailBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
@@ -39,6 +39,7 @@ import com.habitrpg.android.habitica.ui.viewmodels.PartyViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.LoadingButtonState
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
+import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaBottomSheetDialog
import com.habitrpg.android.habitica.ui.views.social.PartySeekingListItem
import com.habitrpg.common.habitica.extensions.DataBindingUtils
import com.habitrpg.common.habitica.extensions.dpToPx
@@ -52,6 +53,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
+import java.text.DecimalFormat
import javax.inject.Inject
@AndroidEntryPoint
@@ -146,6 +148,10 @@ class PartyDetailFragment : BaseFragment() {
viewModel.getGroupData().observe(viewLifecycleOwner) { updateParty(it) }
viewModel.user.observe(viewLifecycleOwner) { updateUser(it) }
viewModel.getMembersData().observe(viewLifecycleOwner) { updateMembersList(it) }
+
+ binding?.questMechanicsButton?.setOnClickListener {
+ showQuestMechanicsDialog()
+ }
}
private fun refreshParty() {
@@ -168,6 +174,7 @@ class PartyDetailFragment : BaseFragment() {
binding?.newQuestButton?.visibility = View.GONE
binding?.questDetailButton?.visibility = View.VISIBLE
binding?.questImageWrapper?.visibility = View.VISIBLE
+ binding?.questMechanicsButton?.visibility = View.VISIBLE
lifecycleScope.launch(Dispatchers.Main) {
delay(500)
val content =
@@ -181,6 +188,7 @@ class PartyDetailFragment : BaseFragment() {
binding?.questDetailButton?.visibility = View.GONE
binding?.questImageWrapper?.visibility = View.GONE
binding?.questProgressView?.visibility = View.GONE
+ binding?.questMechanicsButton?.visibility = View.GONE
}
binding?.findNewMember?.isVisible = viewModel.isLeader == true
@@ -208,7 +216,14 @@ class PartyDetailFragment : BaseFragment() {
}
}
- binding?.questProgressView?.configure(user, viewModel.isUserOnQuest)
+
+ if (viewModel.isQuestActive && viewModel.isUserOnQuest) {
+ val value = (user.party?.quest?.progress?.up ?: 0F).toDouble()
+ val df = DecimalFormat("###.#")
+ binding?.questPendingDamageView?.text = getString(R.string.damage_pending, df.format(value))
+ val collectedItems = user.party?.quest?.progress?.collectedItems
+ binding?.questPendingItemsView?.text = requireContext().resources.getQuantityString(R.plurals.items_pending, collectedItems ?: 0, collectedItems ?: 0)
+ }
if ((user.invitations?.parties?.count() ?: 0) > 0) {
binding?.partyInvitationWrapper?.visibility = View.VISIBLE
@@ -276,7 +291,7 @@ class PartyDetailFragment : BaseFragment() {
binding?.questParticipationView?.setTextColor(
ContextCompat.getColor(
it,
- R.color.text_quad
+ R.color.text_ternary
)
)
}
@@ -291,6 +306,8 @@ class PartyDetailFragment : BaseFragment() {
if (questParticipants?.find { it.key == viewModel.userViewModel.userID } != null) {
binding?.questParticipationView?.text =
context?.getString(R.string.number_participants, questParticipants.size)
+ binding?.questPendingDamageView?.isVisible = questContent.isBossQuest
+ binding?.questPendingItemsView?.isVisible = !questContent.isBossQuest
} else {
binding?.questParticipationView?.text =
context?.getString(R.string.not_participating)
@@ -304,13 +321,18 @@ class PartyDetailFragment : BaseFragment() {
}
binding?.questImageWrapper?.alpha = 0.5f
binding?.questProgressView?.alpha = 0.5f
+ binding?.questPendingDamageView?.isVisible = false
+ binding?.questPendingItemsView?.isVisible = false
}
+
} else {
binding?.questProgressView?.visibility = View.GONE
val members = viewModel.getGroupData().value?.quest?.members
val responded = members?.filter { it.isParticipating != null }
binding?.questParticipationView?.text =
context?.getString(R.string.number_responded, responded?.size, members?.size)
+ binding?.questPendingDamageView?.isVisible = false
+ binding?.questPendingItemsView?.isVisible = false
}
}
@@ -513,4 +535,10 @@ class PartyDetailFragment : BaseFragment() {
private fun questDetailButtonClicked() {
MainNavigationController.navigate(PartyFragmentDirections.openQuestDetail())
}
+
+ private fun showQuestMechanicsDialog() {
+ val dialog = HabiticaBottomSheetDialog(requireContext())
+ dialog.setContentView(R.layout.quest_mechanics_dialog)
+ dialog.show()
+ }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
index 6971d3f6e4..c431924ebc 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyFragment.kt
@@ -16,7 +16,7 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.ui.activities.GroupFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
@@ -66,7 +66,9 @@ class PartyFragment : BaseMainFragment() {
arguments?.let {
val args = PartyFragmentArgs.fromBundle(it)
- binding?.viewPager?.currentItem = args.tabToOpen
+ binding?.viewPager?.post {
+ binding?.viewPager?.currentItem = args.tabToOpen
+ }
if (args.partyID?.isNotBlank() == true) {
viewModel.setGroupID(args.partyID ?: "")
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt
index 61d27b91ee..151e79347c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartyInviteFragment.kt
@@ -52,7 +52,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.invitations.InviteResponse
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
@@ -259,10 +259,16 @@ fun PartyInviteView(
inviteButtonState = LoadingButtonState.CONTENT
}
}) {
- viewModel.sendInvites()
- inviteButtonState = LoadingButtonState.SUCCESS
- delay(2.toDuration(DurationUnit.SECONDS))
- dismiss()
+ val responses = viewModel.sendInvites()
+ if ((responses?.size ?: 0) > 0) {
+ inviteButtonState = LoadingButtonState.SUCCESS
+ delay(2.toDuration(DurationUnit.SECONDS))
+ dismiss()
+ } else {
+ inviteButtonState = LoadingButtonState.FAILED
+ delay(2.toDuration(DurationUnit.SECONDS))
+ inviteButtonState = LoadingButtonState.CONTENT
+ }
}
}
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt
index a61ed2bfb7..39603f947e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/party/PartySeekingFragment.kt
@@ -50,12 +50,13 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.paging.compose.collectAsLazyPagingItems
-import androidx.paging.compose.items
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.databinding.FragmentComposeBinding
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.invitations.InviteResponse
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
@@ -140,7 +141,7 @@ class PartySeekingFragment : BaseFragment() {
override fun onStart() {
super.onStart()
- AmplitudeManager.sendEvent("View Find Members", AmplitudeManager.EVENT_CATEGORY_NAVIGATION, AmplitudeManager.EVENT_HITTYPE_EVENT)
+ Analytics.sendEvent("View Find Members", EventCategory.NAVIGATION, HitType.EVENT)
}
}
@@ -250,13 +251,13 @@ fun PartySeekingView(
}
}
items(
- items = pageData
+ pageData.itemCount
) {
- if (it == null) return@items
+ val item = pageData[it] ?: return@items
PartySeekingListItem(
- user = it,
- inviteState = viewModel.inviteStates[it.id]?.second ?: LoadingButtonState.CONTENT,
- isInvited = viewModel.inviteStates[it.id]?.first ?: false,
+ user = item,
+ inviteState = viewModel.inviteStates[item.id]?.second ?: LoadingButtonState.CONTENT,
+ isInvited = viewModel.inviteStates[item.id]?.first ?: false,
modifier = Modifier
.animateItemPlacement()
.padding(horizontal = 14.dp)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/BugFixFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/BugFixFragment.kt
index 304ffbbfae..f312273fac 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/BugFixFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/BugFixFragment.kt
@@ -14,7 +14,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentSupportBugFixBinding
import com.habitrpg.android.habitica.databinding.KnownIssueBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.extensions.layoutInflater
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt
index 64cef35921..eb1b4557fd 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt
@@ -25,7 +25,7 @@ import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.databinding.FragmentFaqOverviewBinding
import com.habitrpg.android.habitica.databinding.SupportFaqItemBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt
index cf9ce32749..8ff73a4775 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/SupportMainFragment.kt
@@ -11,7 +11,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.FAQRepository
import com.habitrpg.android.habitica.databinding.FragmentSupportMainBinding
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.helpers.ExceptionHandler
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt
index f4e83fc121..76c3d06205 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/RewardsRecyclerviewFragment.kt
@@ -65,6 +65,12 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
if (showCustomRewards) {
lifecycleScope.launchCatching {
inventoryRepository.getInAppRewards().collect {
+ val user = viewModel.user.value
+ (recyclerAdapter as? RewardsRecyclerViewAdapter)?.goldGemsLeft = if (user?.isSubscribed == true) {
+ user.purchased?.plan?.numberOfGemsLeft
+ } else {
+ -1
+ }
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.updateItemRewards(it)
}
}
@@ -78,7 +84,7 @@ class RewardsRecyclerviewFragment : TaskRecyclerViewFragment() {
(recyclerAdapter as? RewardsRecyclerViewAdapter)?.onShowPurchaseDialog = { item, isPinned ->
val dialog = PurchaseDialog(requireContext(), userRepository, inventoryRepository, item)
dialog.isPinned = isPinned
- dialog.onGearPurchased = {
+ dialog.onShopNeedsRefresh = {
viewModel.refreshData { }
}
dialog.show()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt
index 01856b6bb3..07335b8edf 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TaskRecyclerViewFragment.kt
@@ -23,7 +23,7 @@ import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
index b14742a71e..e468501c56 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.kt
@@ -24,8 +24,10 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding
import com.habitrpg.android.habitica.extensions.setTintWith
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
@@ -324,7 +326,7 @@ class TasksFragment : BaseMainFragment(), SearchView.O
3 -> TaskType.REWARD
else -> ""
}
- AmplitudeManager.sendEvent("open create task form", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_EVENT, additionalData)
+ Analytics.sendEvent("open create task form", EventCategory.BEHAVIOUR, HitType.EVENT, additionalData)
val bundle = Bundle()
bundle.putString(TaskFormActivity.TASK_TYPE_KEY, type.value)
@@ -362,10 +364,10 @@ class TasksFragment : BaseMainFragment(), SearchView.O
}
if (!DateUtils.isToday(viewModel.sharedPreferences.getLong("last_creation_reporting", 0))) {
- AmplitudeManager.sendEvent(
+ Analytics.sendEvent(
"task created",
- AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
- AmplitudeManager.EVENT_HITTYPE_EVENT
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT
)
viewModel.sharedPreferences.edit {
putLong("last_creation_reporting", Date().time)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt
index 0bc6bae050..5bb436e8d1 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/MountViewHolder.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.viewHolders
+import android.app.Activity
import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import android.view.View
@@ -11,7 +12,11 @@ import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.android.habitica.models.inventory.Mount
import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu
import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem
+import com.habitrpg.android.habitica.ui.views.showAsBottomSheet
+import com.habitrpg.android.habitica.ui.views.stable.MountBottomSheet
+import com.habitrpg.android.habitica.ui.views.stable.PetBottomSheet
import com.habitrpg.common.habitica.extensions.DataBindingUtils
+import dagger.hilt.android.internal.managers.ViewComponentManager
class MountViewHolder(parent: ViewGroup, private val onEquip: ((String) -> Unit)?) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.mount_overview_item)), View.OnClickListener {
private var binding: MountOverviewItemBinding = MountOverviewItemBinding.bind(itemView)
@@ -51,16 +56,20 @@ class MountViewHolder(parent: ViewGroup, private val onEquip: ((String) -> Unit)
if (!owned) {
return
}
- val menu = BottomSheetMenu(itemView.context)
- menu.setTitle(animal?.text)
- menu.setImage("stable_Mount_Icon_" + animal?.animal + "-" + animal?.color)
-
- val hasCurrentMount = currentMount.equals(animal?.key)
- val labelId = if (hasCurrentMount) R.string.unequip else R.string.equip
- menu.addMenuItem(BottomSheetMenuItem(resources.getString(labelId)))
- menu.setSelectionRunnable {
- animal?.let { onEquip?.invoke(it.key ?: "") }
+ val context = itemView.context
+ animal?.let { pet ->
+ (if (context is ViewComponentManager.FragmentContextWrapper) {
+ context.baseContext
+ } else {
+ context
+ }as Activity).showAsBottomSheet {
+ MountBottomSheet(
+ pet,
+ currentMount.equals(animal?.key),
+ onEquip,
+ it
+ )
+ }
}
- menu.show()
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt
index be791b6f7a..f7116196c4 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/PetViewHolder.kt
@@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.viewHolders
+import android.app.Activity
import android.graphics.PorterDuff
import android.graphics.drawable.BitmapDrawable
import android.view.View
@@ -13,15 +14,17 @@ import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.models.inventory.Pet
-import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu
-import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem
import com.habitrpg.android.habitica.ui.views.dialogs.PetSuggestHatchDialog
+import com.habitrpg.android.habitica.ui.views.showAsBottomSheet
+import com.habitrpg.android.habitica.ui.views.stable.PetBottomSheet
import com.habitrpg.common.habitica.extensions.DataBindingUtils
+import com.habitrpg.shared.habitica.models.responses.FeedResponse
+import dagger.hilt.android.internal.managers.ViewComponentManager
class PetViewHolder(
parent: ViewGroup,
private val onEquip: ((String) -> Unit)?,
- private val onFeed: ((Pet, Food?) -> Unit)?,
+ private val onFeed: (suspend (Pet, Food?) -> FeedResponse?)?,
private val ingredientsReceiver: ((Animal, ((Pair) -> Unit)) -> Unit)?
) : androidx.recyclerview.widget.RecyclerView.ViewHolder(parent.inflate(R.layout.pet_detail_item)),
View.OnClickListener {
@@ -32,6 +35,7 @@ class PetViewHolder(
private var potionCount: Int = 0
private var ownsSaddles = false
private var animal: Pet? = null
+ private var trained: Int = 0
private var currentPet: String? = null
private var binding: PetDetailItemBinding = PetDetailItemBinding.bind(itemView)
@@ -57,6 +61,7 @@ class PetViewHolder(
currentPet: String?
) {
this.animal = item
+ this.trained = trained
isOwned = trained > 0
binding.imageView.alpha = 1.0f
this.canRaiseToMount = canRaiseToMount
@@ -97,7 +102,7 @@ class PetViewHolder(
DataBindingUtils.loadImage(itemView.context, imageName) {
val resources = itemView.context.resources ?: return@loadImage
val drawable =
- if (trained == 0) BitmapDrawable(resources, it.toBitmap().extractAlpha()) else it
+ if (trained == 0 && canRaiseToMount) BitmapDrawable(resources, it.toBitmap().extractAlpha()) else it
if (binding.imageView.tag == imageName) {
binding.imageView.bitmap = drawable.toBitmap()
}
@@ -110,39 +115,24 @@ class PetViewHolder(
return
}
val context = itemView.context
- val menu = BottomSheetMenu(context)
- menu.setTitle(animal?.text)
- menu.setImage("stable_Pet-${animal?.animal}-${animal?.color}")
-
- val hasCurrentPet = currentPet.equals(animal?.key)
- val labelId = if (hasCurrentPet) R.string.unequip else R.string.equip
- menu.addMenuItem(BottomSheetMenuItem(itemView.resources.getString(labelId)))
-
- if (canRaiseToMount) {
- menu.addMenuItem(BottomSheetMenuItem(itemView.resources.getString(R.string.feed)))
- if (ownsSaddles) {
- menu.addMenuItem(BottomSheetMenuItem(itemView.resources.getString(R.string.use_saddle)))
- }
- }
- menu.setSelectionRunnable { index ->
- val pet = animal ?: return@setSelectionRunnable
- when (index) {
- 0 -> {
- animal?.let {
- onEquip?.invoke(it.key ?: "")
- }
- }
- 1 -> {
- onFeed?.invoke(pet, null)
- }
- 2 -> {
- val saddle = Food()
- saddle.key = "Saddle"
- onFeed?.invoke(pet, saddle)
- }
+ animal?.let { pet ->
+ (if (context is ViewComponentManager.FragmentContextWrapper) {
+ context.baseContext
+ } else {
+ context
+ }as Activity).showAsBottomSheet {
+ PetBottomSheet(
+ pet,
+ trained,
+ currentPet.equals(animal?.key),
+ canRaiseToMount,
+ ownsSaddles,
+ onEquip,
+ onFeed,
+ it
+ )
}
}
- menu.show()
}
private fun showRequirementsDialog() {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ShopItemViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ShopItemViewHolder.kt
index c022b63abc..3ddaa9cb3b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ShopItemViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/ShopItemViewHolder.kt
@@ -4,18 +4,22 @@ import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.view.View
import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
+import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.RowShopitemBinding
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.common.habitica.extensions.dpToPx
-import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
import com.habitrpg.common.habitica.extensions.loadImage
class ShopItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val binding = RowShopitemBinding.bind(itemView)
var shopIdentifier: String? = null
private var item: ShopItem? = null
+ var limitedNumberLeft: Int? = null
+
var onNeedsRefresh: (() -> Unit)? = null
var onShowPurchaseDialog: ((ShopItem, Boolean) -> Unit)? = null
@@ -26,7 +30,13 @@ class ShopItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), Vi
var isPinned = false
set(value) {
field = value
- binding.pinIndicator.visibility = if (isPinned) View.VISIBLE else View.GONE
+ binding.pinIndicator.visibility = if (field) View.VISIBLE else View.GONE
+ }
+
+ var isCompleted = false
+ set(value) {
+ field = value
+ binding.completedIndicator.visibility = if (field) View.VISIBLE else View.GONE
}
init {
@@ -58,23 +68,32 @@ class ShopItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), Vi
binding.priceLabel.visibility = View.GONE
binding.unlockLabel.visibility = View.VISIBLE
}
+ val isLimited = item.isLimited || item.event?.end != null
if (numberOwned > 0) {
binding.itemDetailIndicator.text = numberOwned.toString()
- binding.itemDetailIndicator.background = if (context.isUsingNightModeResources()) {
- BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfItemIndicatorNumberDark(item.isLimited || item.event?.end != null))
- } else {
- BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfItemIndicatorNumber(item.isLimited || item.event?.end != null))
- }
+ binding.itemDetailIndicator.background =
+ AppCompatResources.getDrawable(context, R.drawable.pill_bg_gray)
binding.itemDetailIndicator.visibility = View.VISIBLE
} else if (item.locked) {
- binding.itemDetailIndicator.background = if (context.isUsingNightModeResources()) {
- BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfItemIndicatorLockedDark(item.isLimited || item.event?.end != null))
+ binding.itemDetailIndicator.background = AppCompatResources.getDrawable(context, R.drawable.shop_locked)
+ binding.itemDetailIndicator.visibility = View.VISIBLE
+ } else if (isLimited) {
+ if (numberOwned == 0) {
+ binding.itemDetailIndicator.background = BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfItemIndicatorLimited())
} else {
- BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfItemIndicatorLocked(item.isLimited || item.event?.end != null))
+ binding.itemDetailIndicator.background = AppCompatResources.getDrawable(context, R.drawable.pill_bg_purple_300)
}
binding.itemDetailIndicator.visibility = View.VISIBLE
- } else if (item.isLimited || item.event?.end != null) {
- binding.itemDetailIndicator.background = BitmapDrawable(context.resources, HabiticaIconsHelper.imageOfItemIndicatorLimited())
+ }
+
+ val limitedLeft = item.limitedNumberLeft ?: limitedNumberLeft
+ if (item.key == "gem" && limitedLeft == -1) {
+ binding.itemDetailIndicator.background = AppCompatResources.getDrawable(context, R.drawable.item_indicator_subscribe)
+ binding.itemDetailIndicator.visibility = View.VISIBLE
+ } else if (item.key == "gem") {
+ binding.itemDetailIndicator.background = AppCompatResources.getDrawable(context, R.drawable.pill_bg_green)
+ binding.itemDetailIndicator.text = "$limitedLeft"
+ binding.itemDetailIndicator.setTextColor(ContextCompat.getColor(context, R.color.white))
binding.itemDetailIndicator.visibility = View.VISIBLE
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt
index fc5bf97a41..a936718454 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewHolders/tasks/BaseTaskViewHolder.kt
@@ -156,7 +156,6 @@ abstract class BaseTaskViewHolder constructor(
if (data.notes?.isNotEmpty() == true) {
notesTextView?.visibility = View.VISIBLE
notesTextView?.setTextColor(ContextCompat.getColor(context, R.color.text_ternary))
- // expandNotesButton.visibility = if (notesTextView.hadEllipses() || notesExpanded) View.VISIBLE else View.GONE
} else {
notesTextView?.visibility = View.GONE
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
index d80dd0085f..e9b7ae7d70 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/AuthenticationViewModel.kt
@@ -20,10 +20,10 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ApiClient
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.extensions.addCloseButton
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.api.HostConfig
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.KeyHelper
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.common.habitica.models.auth.UserAuthResponse
@@ -37,7 +37,6 @@ class AuthenticationViewModel @Inject constructor(
val sharedPrefs: SharedPreferences,
val authenticationHandler: AuthenticationHandler,
val hostConfig: HostConfig,
- val analyticsManager: AnalyticsManager,
private val keyHelper: KeyHelper?
) {
var googleEmail: String? = null
@@ -145,7 +144,7 @@ class AuthenticationViewModel @Inject constructor(
try {
saveTokens(userAuthResponse.apiToken, userAuthResponse.id)
} catch (e: Exception) {
- analyticsManager.logException(e)
+ Analytics.logException(e)
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt
index 9e34805a66..33f0a4986f 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/GroupViewModel.kt
@@ -10,7 +10,7 @@ import androidx.lifecycle.viewModelScope
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge
@@ -39,7 +39,6 @@ import kotlin.time.toDuration
enum class GroupViewType(internal val order: String) {
PARTY("party"),
GUILD("guild"),
- TAVERN("tavern")
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -209,7 +208,7 @@ open class GroupViewModel @Inject constructor(
fun markMessagesSeen() {
groupID?.let {
- if (groupViewType != GroupViewType.TAVERN && it.isNotEmpty() && gotNewMessages) {
+ if (it.isNotEmpty() && gotNewMessages) {
viewModelScope.launchCatching {
socialRepository.markMessagesSeen(it)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt
index e5154f3c9d..1a975f07b7 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/InboxViewModel.kt
@@ -73,7 +73,7 @@ class InboxViewModel @Inject constructor(
setMemberID(recipientID)
} else if (recipientUsername?.isNotBlank() == true) {
viewModelScope.launch(ExceptionHandler.coroutine()) {
- val member = socialRepository.retrieveMemberWithUsername(recipientUsername, false)
+ val member = socialRepository.retrieveMember(recipientUsername, false)
setMemberID(member?.id ?: "")
invalidateDataSource()
dataSourceFactory.updateRecipientID(memberID)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt
index d8f6257a42..9df149b78d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/MainActivityViewModel.kt
@@ -11,14 +11,15 @@ import com.habitrpg.android.habitica.data.ContentRepository
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.ui.TutorialView
import com.habitrpg.common.habitica.api.HostConfig
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse
@@ -40,7 +41,6 @@ class MainActivityViewModel @Inject constructor(
val taskRepository: TaskRepository,
val inventoryRepository: InventoryRepository,
val taskAlarmManager: TaskAlarmManager,
- val analyticsManager: AnalyticsManager,
val maintenanceService: MaintenanceApiService
) : BaseViewModel(userRepository, userViewModel), TutorialView.OnTutorialReaction {
@@ -77,7 +77,7 @@ class MainActivityViewModel @Inject constructor(
)
}
} catch (e: Exception) {
- analyticsManager.logException(e)
+ Analytics.logException(e)
}
}
@@ -94,19 +94,19 @@ class MainActivityViewModel @Inject constructor(
viewModelScope.launch(ExceptionHandler.coroutine()) {
contentRepository.retrieveWorldState()
userRepository.retrieveUser(true, forced)?.let { user ->
- analyticsManager.setUserProperty(
+ Analytics.setUserProperty(
"has_party",
if (user.party?.id?.isNotEmpty() == true) "true" else "false"
)
- analyticsManager.setUserProperty(
+ Analytics.setUserProperty(
"is_subscribed",
if (user.isSubscribed) "true" else "false"
)
- analyticsManager.setUserProperty(
+ Analytics.setUserProperty(
"checkin_count",
user.loginIncentives.toString()
)
- analyticsManager.setUserProperty("level", user.stats?.lvl?.toString() ?: "")
+ Analytics.setUserProperty("level", user.stats?.lvl?.toString() ?: "")
pushNotificationManager.setUser(user)
if (!pushNotificationManager.notificationPermissionEnabled()) {
if (sharedPreferences.getBoolean("usePushNotifications", true)) {
@@ -146,10 +146,10 @@ class MainActivityViewModel @Inject constructor(
additionalData["eventLabel"] = step.identifier + "-android"
additionalData["eventValue"] = step.identifier ?: ""
additionalData["complete"] = complete
- AmplitudeManager.sendEvent(
+ Analytics.sendEvent(
"tutorial",
- AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
- AmplitudeManager.EVENT_HITTYPE_EVENT,
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT,
additionalData
)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
index 6aae10ee53..e0e31108bc 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/NotificationsViewModel.kt
@@ -6,7 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.models.social.UserParty
import com.habitrpg.android.habitica.models.user.User
@@ -286,9 +286,16 @@ open class NotificationsViewModel @Inject constructor(
navController: MainNavigationController
) {
val data = notification.data as? ItemReceivedData
+ if (data?.destination?.startsWith("/") == true) {
+ MainNavigationController.navigate(data.destination ?: "")
+ return
+ }
when (data?.destination) {
"equipment" -> navController.navigate(R.id.equipmentOverviewFragment)
"customization" -> navController.navigate(R.id.avatarCustomizationFragment)
+ "stable" -> navController.navigate(R.id.stableFragment)
+ "pets" -> navController.navigate(R.id.stableFragment)
+ "mounts" -> navController.navigate(R.id.stableFragment)
else -> navController.navigate(R.id.itemsFragment)
}
}
@@ -301,6 +308,7 @@ open class NotificationsViewModel @Inject constructor(
if (isPartyMessage(data)) {
val bundle = Bundle()
bundle.putString("groupID", data?.group?.id)
+ bundle.putInt("tabToOpen", 1)
navController.navigate(R.id.partyFragment, bundle)
} else {
val bundle = Bundle()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt
index 1af114a432..6605eb65c0 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/viewmodels/TasksViewModel.kt
@@ -10,9 +10,11 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.GroupPlanInfoProvider
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.helpers.ExceptionHandler
@@ -116,10 +118,10 @@ class TasksViewModel @Inject constructor(
) { result ->
onResult(result, task.value.toInt())
if (!DateUtils.isToday(sharedPreferences.getLong("last_task_reporting", 0))) {
- AmplitudeManager.sendEvent(
+ Analytics.sendEvent(
"task scored",
- AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
- AmplitudeManager.EVENT_HITTYPE_EVENT
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT
)
sharedPreferences.edit {
putLong("last_task_reporting", Date().time)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
index 63edb5461d..3b8ee60de2 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
@@ -50,7 +50,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.auth.LocalAuthentication
import com.habitrpg.android.habitica.models.members.Member
@@ -67,7 +67,7 @@ import com.habitrpg.shared.habitica.models.Avatar
import kotlin.random.Random
@Composable
-fun UserLevelText(user: Avatar) {
+fun UserLevelText(user : Avatar) {
val text = if (user.hasClass) {
stringResource(
id = R.string.user_level_with_class,
@@ -88,7 +88,7 @@ fun UserLevelText(user: Avatar) {
)
}
-fun getTranslatedClassName(resources: Resources, className: String?): String {
+fun getTranslatedClassName(resources : Resources, className : String?) : String {
return when (className) {
Stats.HEALER -> resources.getString(R.string.healer)
Stats.ROGUE -> resources.getString(R.string.rogue)
@@ -101,14 +101,16 @@ fun getTranslatedClassName(resources: Resources, className: String?): String {
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AppHeaderView(
- user: Avatar?,
- modifier: Modifier = Modifier,
- isMyProfile: Boolean = false,
- teamPlan: TeamPlan? = null,
- teamPlanMembers: List? = null,
- onMemberRowClicked: () -> Unit,
+ user : Avatar?,
+ modifier : Modifier = Modifier,
+ isMyProfile : Boolean = false,
+ teamPlan : TeamPlan? = null,
+ teamPlanMembers : List? = null,
+ onAvatarClicked: (() -> Unit)? = null,
+ onMemberRowClicked : () -> Unit,
onClassSelectionClicked: () -> Unit
) {
+ val isPlayerOptedOutOfClass = user?.preferences?.disableClasses ?: false
Column(modifier) {
Row {
ComposableAvatarView(
@@ -117,7 +119,7 @@ fun AppHeaderView(
.size(110.dp, 100.dp)
.padding(end = 16.dp)
.clickable {
- MainNavigationController.navigate(R.id.avatarOverviewFragment)
+ onAvatarClicked?.invoke()
}
)
val animationValue =
@@ -176,7 +178,7 @@ fun AppHeaderView(
disabled = true,
modifier = Modifier.weight(1f)
)
- } else if (user?.preferences?.disableClasses != true && isMyProfile) {
+ } else if (user?.hasClass == false && isMyProfile && isPlayerOptedOutOfClass == false) {
HabiticaButton(
background = HabiticaTheme.colors.basicButtonColor(),
color = MaterialTheme.colors.onPrimary,
@@ -222,8 +224,7 @@ fun AppHeaderView(
}
) {
Image(
- painterResource(R.drawable.icon_chat),
- null,
+ painterResource(R.drawable.icon_chat), null,
colorFilter = ColorFilter.tint(
colorResource(R.color.text_ternary)
)
@@ -237,15 +238,13 @@ fun AppHeaderView(
exit = slideOutVertically { animHeight } + fadeOut(),
modifier = Modifier.align(Alignment.BottomCenter)
) {
- AnimatedContent(
- targetState = teamPlanMembers?.filter { it.id != user?.id },
+ AnimatedContent(targetState = teamPlanMembers?.filter { it.id != user?.id },
transitionSpec = {
ContentTransform(
- targetContentEnter = fadeIn(animationSpec = tween(200, easing = FastOutSlowInEasing)) + slideInVertically { height -> height },
+ targetContentEnter = fadeIn(animationSpec = tween(200, easing = FastOutSlowInEasing)) + slideInVertically { height -> height },
initialContentExit = fadeOut(animationSpec = tween(200)) + slideOutVertically { height -> -height }
)
- }
- ) { members ->
+ }) {members ->
Row(
horizontalArrangement = Arrangement.spacedBy(
12.dp,
@@ -333,7 +332,7 @@ fun AppHeaderView(
private class UserProvider : PreviewParameterProvider> {
- private fun generateMember(): User {
+ private fun generateMember() : User {
val member = User()
member.profile = Profile()
member.profile?.name = "User"
@@ -358,7 +357,7 @@ private class UserProvider : PreviewParameterProvider> {
return member
}
- override val values: Sequence>
+ override val values : Sequence>
get() {
val list = mutableListOf>()
val earlyMember = generateMember()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AvatarCircleShape.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AvatarCircleShape.kt
deleted file mode 100644
index ee5d711e3b..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AvatarCircleShape.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.habitrpg.android.habitica.ui.views
-
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Outline
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.LayoutDirection
-
-object AvatarCircleShape : Shape {
- override fun createOutline(
- size: Size,
- layoutDirection: LayoutDirection,
- density: Density
- ): Outline =
- CircleShape.createOutline(size, 20f, 20f, 20f, 20f, layoutDirection)
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BackgroundScene.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BackgroundScene.kt
new file mode 100644
index 0000000000..c9edb3bbb2
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BackgroundScene.kt
@@ -0,0 +1,73 @@
+package com.habitrpg.android.habitica.ui.views
+
+import android.graphics.Bitmap
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageShader
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.TileMode
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.res.imageResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import com.habitrpg.android.habitica.R
+import java.util.Calendar
+
+@Composable
+private fun getBackgroundPainter(): ImageBitmap {
+ val calendar = Calendar.getInstance()
+ val month = calendar.get(Calendar.MONTH)
+ return ImageBitmap.imageResource(
+ when (month) {
+ Calendar.JANUARY -> R.drawable.stable_tile_janurary
+ Calendar.FEBRUARY -> R.drawable.stable_tile_february
+ Calendar.MARCH -> R.drawable.stable_tile_march
+ Calendar.APRIL -> R.drawable.stable_tile_april
+ Calendar.MAY -> R.drawable.stable_tile_may
+ Calendar.JUNE -> R.drawable.stable_tile_june
+ Calendar.JULY -> R.drawable.stable_tile_july
+ Calendar.AUGUST -> R.drawable.stable_tile_august
+ Calendar.SEPTEMBER -> R.drawable.stable_tile_september
+ Calendar.OCTOBER -> R.drawable.stable_tile_october
+ Calendar.NOVEMBER -> R.drawable.stable_tile_november
+ Calendar.DECEMBER -> R.drawable.stable_tile_december
+ else -> R.drawable.stable_tile_may
+ }
+ )
+}
+
+@Composable
+fun BackgroundScene(modifier: Modifier = Modifier) {
+ val image = getBackgroundPainter()
+ Canvas(
+ modifier = modifier
+ .height(124.dp)
+ .fillMaxWidth()
+ .zIndex(1f), onDraw = {
+ val bitmap = Bitmap.createScaledBitmap(
+ image.asAndroidBitmap(),
+ image.width.dp.roundToPx(),
+ 124.dp.roundToPx(),
+ false
+ )
+ val paint = Paint().asFrameworkPaint().apply {
+ isAntiAlias = true
+ shader = ImageShader(
+ bitmap.asImageBitmap(),
+ TileMode.Repeated,
+ TileMode.Repeated
+ )
+ }
+ drawIntoCanvas {
+ it.nativeCanvas.drawPaint(paint)
+ }
+ paint.reset()
+ })
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt
index ace971c784..e0aaa96d03 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/BottomSheetUtils.kt
@@ -33,6 +33,7 @@ import androidx.fragment.app.Fragment
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// Extension for Activity
@@ -71,7 +72,7 @@ private fun BottomSheetWrapper(
content: @Composable (() -> Unit) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
- val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
+ val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, skipHalfExpanded = true)
var isSheetOpened by remember { mutableStateOf(false) }
val systemUiController = rememberSystemUiController()
@@ -84,7 +85,7 @@ private fun BottomSheetWrapper(
val radius = 20.dp
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
- scrimColor = colorResource(R.color.content_background).copy(alpha = 0.3f),
+ scrimColor = colorResource(R.color.content_background).copy(alpha = 0.5f),
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = radius, topEnd = radius),
sheetContent = {
@@ -92,13 +93,8 @@ private fun BottomSheetWrapper(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(horizontal = 4.dp)
- .border(
- 2.dp,
- colorResource(R.color.window_background),
- RoundedCornerShape(topStart = radius, topEnd = radius)
- )
.background(
- MaterialTheme.colors.background,
+ HabiticaTheme.colors.windowBackground,
RoundedCornerShape(topStart = radius, topEnd = radius)
)
.padding(vertical = 8.dp)
@@ -136,7 +132,10 @@ private fun BottomSheetWrapper(
}
else -> {
isSheetOpened = true
- modalBottomSheetState.show()
+ coroutineScope.launch {
+ delay(100L)
+ modalBottomSheetState.show()
+ }
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaIconsHelper.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaIconsHelper.java
index c912f7d4f8..1fc3503529 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaIconsHelper.java
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaIconsHelper.java
@@ -234,7 +234,7 @@ public static Bitmap imageOfPinnedItem() {
if (imageOfPinnedItem != null)
return imageOfPinnedItem;
- int size = scaleSize(16);
+ int size = scaleSize(14);
imageOfPinnedItem = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(imageOfPinnedItem);
canvas.scale(displayDensity, displayDensity);
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt
index 365b04b8dc..452ff55f83 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/HabiticaSnackbar.kt
@@ -15,6 +15,8 @@ import androidx.core.view.ViewCompat
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.SnackbarViewBinding
+import com.habitrpg.common.habitica.helpers.Animations
import com.plattysoft.leonids.ParticleSystem
class HabiticaSnackbar
@@ -25,19 +27,29 @@ class HabiticaSnackbar
* @param content The content view for this transient bottom bar.
* @param callback The content view callback for this transient bottom bar.
*/
-private constructor(parent: ViewGroup, content: View, callback: ContentViewCallback) : BaseTransientBottomBar(parent, content, callback) {
+private constructor(parent: ViewGroup, content: View, callback: ContentViewCallback) :
+ BaseTransientBottomBar(parent, content, callback) {
+ val binding: SnackbarViewBinding = SnackbarViewBinding.bind(content)
fun setTitle(title: CharSequence?): HabiticaSnackbar {
- val textView = view.findViewById(R.id.snackbar_title) as? TextView
- textView?.text = title
- textView?.visibility = if (title != null) View.VISIBLE else View.GONE
+ binding.snackbarTitle.text = title
+ binding.snackbarTitle.visibility = if (title != null) View.VISIBLE else View.GONE
return this
}
fun setText(text: CharSequence?): HabiticaSnackbar {
- val textView = view.findViewById(R.id.snackbar_text) as? TextView
- textView?.text = text
- textView?.visibility = if (text != null) View.VISIBLE else View.GONE
+ binding.snackbarText.text = text
+ binding.snackbarText.visibility = if (text != null) View.VISIBLE else View.GONE
+ return this
+ }
+
+ fun setTitleColor(color: Int): HabiticaSnackbar {
+ binding.snackbarTitle.setTextColor(color)
+ return this
+ }
+
+ fun setTextColor(color: Int): HabiticaSnackbar {
+ binding.snackbarText.setTextColor(color)
return this
}
@@ -45,20 +57,16 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
if (icon == null) {
return this
}
- val rightView = view.findViewById(R.id.rightView)
- rightView.visibility = View.VISIBLE
- val rightIconView = view.findViewById(R.id.rightIconView)
- rightIconView.setImageDrawable(icon)
- val rightTextView = view.findViewById(R.id.rightTextView)
- rightTextView.setTextColor(textColor)
- rightTextView.text = text
+ binding.rightView.visibility = View.VISIBLE
+ binding.rightIconView.setImageDrawable(icon)
+ binding.rightTextView.setTextColor(textColor)
+ binding.rightTextView.text = text
return this
}
fun setLeftIcon(image: Drawable?): HabiticaSnackbar {
- val imageView = view.findViewById(R.id.leftImageView)
- imageView.setImageDrawable(image)
- imageView.visibility = if (image != null) View.VISIBLE else View.GONE
+ binding.leftImageView.setImageDrawable(image)
+ binding.leftImageView.visibility = if (image != null) View.VISIBLE else View.GONE
return this
}
@@ -68,42 +76,47 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
}
fun setBackgroundResource(resourceId: Int): HabiticaSnackbar {
- val snackbarView = view.findViewById(R.id.snackbar_view)
- snackbarView.setBackgroundResource(resourceId)
+ binding.snackbarView.setBackgroundResource(resourceId)
view.setBackgroundColor(ContextCompat.getColor(context, R.color.transparent))
return this
}
private fun setSpecialView(specialView: View?): HabiticaSnackbar {
if (specialView != null) {
- val snackbarView = view.findViewById(R.id.content_container) as? LinearLayout
- snackbarView?.addView(specialView)
+ binding.contentContainer.addView(specialView)
}
return this
}
- private class ContentViewCallback(private val content: View) : com.google.android.material.snackbar.ContentViewCallback {
+ private class ContentViewCallback(private val content: View) :
+ com.google.android.material.snackbar.ContentViewCallback {
@Suppress("SameParameterValue")
override fun animateContentIn(delay: Int, duration: Int) {
content.scaleY = 0f
content.scaleX = 0f
- ViewCompat.animate(content).scaleY(1f).setDuration(duration.toLong()).startDelay = delay.toLong()
- ViewCompat.animate(content).scaleX(1f).setDuration(duration.toLong()).startDelay = delay.toLong()
- ViewCompat.animate(content).alpha(1f).setDuration(duration.toLong()).startDelay = delay.toLong()
+ ViewCompat.animate(content).scaleY(1f).setDuration(duration.toLong()).startDelay =
+ delay.toLong()
+ ViewCompat.animate(content).scaleX(1f).setDuration(duration.toLong()).startDelay =
+ delay.toLong()
+ ViewCompat.animate(content).alpha(1f).setDuration(duration.toLong()).startDelay =
+ delay.toLong()
}
override fun animateContentOut(delay: Int, duration: Int) {
content.scaleY = 1f
content.scaleX = 1f
- ViewCompat.animate(content).scaleY(0f).setDuration(duration.toLong()).startDelay = delay.toLong()
- ViewCompat.animate(content).scaleX(0f).setDuration(duration.toLong()).startDelay = delay.toLong()
- ViewCompat.animate(content).alpha(0f).setDuration(duration.toLong()).startDelay = delay.toLong()
+ ViewCompat.animate(content).scaleY(0f).setDuration(duration.toLong()).startDelay =
+ delay.toLong()
+ ViewCompat.animate(content).scaleX(0f).setDuration(duration.toLong()).startDelay =
+ delay.toLong()
+ ViewCompat.animate(content).alpha(0f).setDuration(duration.toLong()).startDelay =
+ delay.toLong()
}
}
enum class SnackbarDisplayType {
- NORMAL, FAILURE, FAILURE_BLUE, DROP, SUCCESS, BLUE
+ NORMAL, FAILURE, FAILURE_BLUE, DROP, SUCCESS, BLUE, BLACK, SUBSCRIBER_BENEFIT
}
companion object {
@@ -123,9 +136,24 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
container: ViewGroup,
content: CharSequence?,
displayType: SnackbarDisplayType,
- isCelebratory: Boolean = false
+ isCelebratory: Boolean = false,
+ isSubscriberBenefit: Boolean = false,
+ duration: Int = Snackbar.LENGTH_LONG
) {
- showSnackbar(container, null, null, content, null, null, 0, null, displayType, isCelebratory)
+ showSnackbar(
+ container,
+ null,
+ null,
+ content,
+ null,
+ null,
+ 0,
+ null,
+ displayType,
+ isCelebratory,
+ isSubscriberBenefit,
+ duration
+ )
}
fun showSnackbar(
@@ -134,9 +162,24 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
title: CharSequence?,
content: CharSequence?,
displayType: SnackbarDisplayType,
- isCelebratory: Boolean = false
+ isCelebratory: Boolean = false,
+ isSubscriberBenefit: Boolean = false,
+ duration: Int = Snackbar.LENGTH_LONG
) {
- showSnackbar(container, leftImage, title, content, null, null, 0, null, displayType, isCelebratory)
+ showSnackbar(
+ container,
+ leftImage,
+ title,
+ content,
+ null,
+ null,
+ 0,
+ null,
+ displayType,
+ isCelebratory,
+ isSubscriberBenefit,
+ duration
+ )
}
fun showSnackbar(
@@ -147,9 +190,24 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
rightTextColor: Int?,
rightText: String,
displayType: SnackbarDisplayType,
- isCelebratory: Boolean = false
+ isCelebratory: Boolean = false,
+ isSubscriberBenefit: Boolean = false,
+ duration: Int = Snackbar.LENGTH_LONG
) {
- showSnackbar(container, null, title, content, null, rightIcon, rightTextColor, rightText, displayType, isCelebratory)
+ showSnackbar(
+ container,
+ null,
+ title,
+ content,
+ null,
+ rightIcon,
+ rightTextColor,
+ rightText,
+ displayType,
+ isCelebratory,
+ isSubscriberBenefit,
+ duration
+ )
}
fun showSnackbar(
@@ -158,9 +216,24 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
content: CharSequence?,
specialView: View?,
displayType: SnackbarDisplayType,
- isCelebratory: Boolean = false
+ isCelebratory: Boolean = false,
+ isSubscriberBenefit: Boolean = false,
+ duration: Int = Snackbar.LENGTH_LONG
) {
- showSnackbar(container, null, title, content, specialView, null, 0, null, displayType, isCelebratory)
+ showSnackbar(
+ container,
+ null,
+ title,
+ content,
+ specialView,
+ null,
+ 0,
+ null,
+ displayType,
+ isCelebratory,
+ isSubscriberBenefit,
+ duration
+ )
}
fun showSnackbar(
@@ -173,62 +246,137 @@ private constructor(parent: ViewGroup, content: View, callback: ContentViewCallb
rightTextColor: Int?,
rightText: String?,
displayType: SnackbarDisplayType,
- isCelebratory: Boolean = false
+ isCelebratory: Boolean = false,
+ isSubscriberBenefit: Boolean = false,
+ duration: Int = Snackbar.LENGTH_LONG
) {
- val snackbar = make(container, Snackbar.LENGTH_LONG)
- .setTitle(title)
- .setText(content)
+ val snackbar = make(container, duration)
.setSpecialView(specialView)
.setLeftIcon(leftImage)
+ if (title?.isNotBlank() == true) {
+ snackbar.setTitle(title)
+ }
+ if (content?.isNotBlank() == true) {
+ if (title?.isNotBlank() != true) {
+ snackbar.setTitle(content)
+ } else {
+ snackbar.setText(content)
+ }
+ }
rightTextColor?.let {
snackbar.setRightDiff(rightIcon, rightTextColor, rightText)
}
when (displayType) {
SnackbarDisplayType.FAILURE -> snackbar.setBackgroundResource(R.drawable.snackbar_background_red)
- SnackbarDisplayType.FAILURE_BLUE, SnackbarDisplayType.BLUE -> snackbar.setBackgroundResource(R.drawable.snackbar_background_blue)
- SnackbarDisplayType.DROP, SnackbarDisplayType.NORMAL -> snackbar.setBackgroundResource(R.drawable.snackbar_background_gray)
+ SnackbarDisplayType.BLACK -> snackbar.setBackgroundResource(R.drawable.snackbar_background_black)
+ SnackbarDisplayType.FAILURE_BLUE, SnackbarDisplayType.BLUE -> snackbar.setBackgroundResource(
+ R.drawable.snackbar_background_blue
+ )
+ SnackbarDisplayType.DROP, SnackbarDisplayType.NORMAL -> snackbar.setBackgroundResource(
+ R.drawable.snackbar_background_gray
+ )
SnackbarDisplayType.SUCCESS -> snackbar.setBackgroundResource(R.drawable.snackbar_background_green)
+ SnackbarDisplayType.SUBSCRIBER_BENEFIT -> {
+ snackbar.setBackgroundResource(R.drawable.subscriber_benefit_snackbar_bg)
+ snackbar.setTitleColor(ContextCompat.getColor(container.context, R.color.green_1))
+ snackbar.setTextColor(ContextCompat.getColor(container.context, R.color.green_1))
+ }
}
- snackbar.show()
-
if (isCelebratory) {
- container.postDelayed(
- {
- ParticleSystem(container, 30, ContextCompat.getDrawable(container.context, R.drawable.confetti_blue), 6000)
- .setAcceleration(0.00070f, 90)
- .setRotationSpeedRange(134f, 164f)
- .setScaleRange(0.8f, 1.2f)
- .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
- .setFadeOut(200, AccelerateInterpolator())
- .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
- ParticleSystem(container, 30, ContextCompat.getDrawable(container.context, R.drawable.confetti_red), 6000)
- .setAcceleration(0.00060f, 90)
- .setRotationSpeedRange(134f, 164f)
- .setScaleRange(0.8f, 1.2f)
- .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
- .setFadeOut(200, AccelerateInterpolator())
- .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
- ParticleSystem(container, 30, ContextCompat.getDrawable(container.context, R.drawable.confetti_yellow), 6000)
- .setAcceleration(0.00070f, 90)
- .setRotationSpeedRange(134f, 164f)
- .setScaleRange(0.8f, 1.2f)
- .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
- .setFadeOut(200, AccelerateInterpolator())
- .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
- ParticleSystem(container, 30, ContextCompat.getDrawable(container.context, R.drawable.confetti_purple), 6000)
- .setAcceleration(0.00090f, 90)
- .setRotationSpeedRange(134f, 164f)
- .setScaleRange(0.8f, 1.2f)
- .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
- .setFadeOut(200, AccelerateInterpolator())
- .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
- },
- 500
- )
+ showConfettiAnimation(container)
+ } else if (isSubscriberBenefit) {
+ showSubscriberBenefitAnimation(container, snackbar)
+ }
+
+ snackbar.show()
+ if (displayType == SnackbarDisplayType.FAILURE || displayType == SnackbarDisplayType.FAILURE_BLUE) {
+ container.postDelayed({
+ snackbar.getView().startAnimation(Animations.negativeShakeAnimation())
+ }, 600L)
}
}
+
+ private fun showSubscriberBenefitAnimation(container: ViewGroup, snackbar: HabiticaSnackbar) {
+ container.postDelayed(
+ {
+ ParticleSystem(
+ container,
+ 300,
+ ContextCompat.getDrawable(container.context, R.drawable.confetti_subs),
+ 800L
+ )
+ .setFadeOut(200L)
+ .setSpeedRange(0.05f, 0.2f)
+ .setScaleRange(0.8f, 1.2f)
+ .setRotationSpeedRange(134f, 164f)
+ .emit(snackbar.getView(), 200, 600)
+ }, 500L
+ )
+ }
+
+ private fun showConfettiAnimation(container: ViewGroup) {
+ container.postDelayed(
+ {
+ ParticleSystem(
+ container,
+ 30,
+ ContextCompat.getDrawable(container.context, R.drawable.confetti_blue),
+ 6000
+ )
+ .setAcceleration(0.00070f, 90)
+ .setRotationSpeedRange(134f, 164f)
+ .setScaleRange(0.8f, 1.2f)
+ .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
+ .setFadeOut(200, AccelerateInterpolator())
+ .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
+ ParticleSystem(
+ container,
+ 30,
+ ContextCompat.getDrawable(container.context, R.drawable.confetti_red),
+ 6000
+ )
+ .setAcceleration(0.00060f, 90)
+ .setRotationSpeedRange(134f, 164f)
+ .setScaleRange(0.8f, 1.2f)
+ .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
+ .setFadeOut(200, AccelerateInterpolator())
+ .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
+ ParticleSystem(
+ container,
+ 30,
+ ContextCompat.getDrawable(
+ container.context,
+ R.drawable.confetti_yellow
+ ),
+ 6000
+ )
+ .setAcceleration(0.00070f, 90)
+ .setRotationSpeedRange(134f, 164f)
+ .setScaleRange(0.8f, 1.2f)
+ .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
+ .setFadeOut(200, AccelerateInterpolator())
+ .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
+ ParticleSystem(
+ container,
+ 30,
+ ContextCompat.getDrawable(
+ container.context,
+ R.drawable.confetti_purple
+ ),
+ 6000
+ )
+ .setAcceleration(0.00090f, 90)
+ .setRotationSpeedRange(134f, 164f)
+ .setScaleRange(0.8f, 1.2f)
+ .setSpeedByComponentsRange(-0.15f, 0.15f, -0.15f, -0.45f)
+ .setFadeOut(200, AccelerateInterpolator())
+ .emitWithGravity(container, Gravity.BOTTOM, 7, 1000)
+ },
+ 500
+ )
+ }
}
}
@@ -247,6 +395,17 @@ interface SnackbarActivity {
displayType: HabiticaSnackbar.SnackbarDisplayType = HabiticaSnackbar.SnackbarDisplayType.NORMAL,
isCelebratory: Boolean = false
) {
- HabiticaSnackbar.showSnackbar(snackbarContainer(), leftImage, title, content, specialView, rightIcon, rightTextColor, rightText, displayType, isCelebratory)
+ HabiticaSnackbar.showSnackbar(
+ snackbarContainer(),
+ leftImage,
+ title,
+ content,
+ specialView,
+ rightIcon,
+ rightTextColor,
+ rightText,
+ displayType,
+ isCelebratory
+ )
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt
index b06d69e3f9..bd4a2e8c68 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/PixelArtView.kt
@@ -1,7 +1,10 @@
package com.habitrpg.android.habitica.ui.views
+import android.graphics.Bitmap
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.viewinterop.AndroidView
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
@@ -26,3 +29,19 @@ fun PixelArtView(
}
)
}
+
+@Composable
+fun PixelArtView(
+ bitmap: ImageBitmap,
+ modifier: Modifier = Modifier,
+) {
+ AndroidView(
+ modifier = modifier, // Occupy the max size in the Compose UI tree
+ factory = { context ->
+ PixelArtView(context)
+ },
+ update = { view ->
+ view.bitmap = bitmap.asAndroidBitmap()
+ }
+ )
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/RoundedCornerLayout.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/RoundedCornerLayout.kt
deleted file mode 100644
index 1ed0422dd3..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/RoundedCornerLayout.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.habitrpg.android.habitica.ui.views
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffXfermode
-import android.graphics.RectF
-import android.util.AttributeSet
-import android.util.TypedValue
-import android.widget.FrameLayout
-
-// https://stackoverflow.com/a/26201117
-class RoundedCornerLayout : FrameLayout {
-
- private var maskBitmap: Bitmap? = null
- private var paint: Paint? = null
- private var maskPaint: Paint? = null
- private var cornerRadius: Float = CORNER_RADIUS
-
- constructor(context: Context) : super(context) {
- init(context)
- }
-
- constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
- init(context)
- }
-
- constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
- init(context)
- }
-
- private fun init(context: Context) {
- val metrics = context.resources.displayMetrics
- cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, metrics)
-
- paint = Paint(Paint.ANTI_ALIAS_FLAG)
-
- maskPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG)
- maskPaint?.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
-
- setWillNotDraw(false)
- }
-
- override fun draw(canvas: Canvas) {
- val offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
- val offscreenCanvas = Canvas(offscreenBitmap)
-
- super.draw(offscreenCanvas)
-
- if (maskBitmap == null) {
- maskBitmap = createMask(width, height)
- }
-
- maskBitmap?.let { offscreenCanvas.drawBitmap(it, 0f, 0f, maskPaint) }
- canvas.drawBitmap(offscreenBitmap, 0f, 0f, paint)
- }
-
- private fun createMask(width: Int, height: Int): Bitmap {
- val mask = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8)
- val canvas = Canvas(mask)
-
- val paint = Paint(Paint.ANTI_ALIAS_FLAG)
- paint.color = Color.WHITE
-
- canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
-
- paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
- canvas.drawRoundRect(RectF(0f, 0f, width.toFloat(), height.toFloat()), cornerRadius, cornerRadius, paint)
-
- return mask
- }
-
- companion object {
- private const val CORNER_RADIUS = 40.0f
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SparkView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SparkView.kt
index d422ebade3..e1f8cb7f7c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SparkView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/SparkView.kt
@@ -83,7 +83,7 @@ class SparkView @JvmOverloads constructor(
setMeasuredDimension(width, height)
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val thisCanvas = canvas ?: return
val centerHorizontal = width / 2f
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDialog.kt
index f061242982..49482fbe66 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/AchievementDialog.kt
@@ -7,7 +7,7 @@ import android.view.View
import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogAchievementDetailBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.extensions.fromHtml
import com.habitrpg.common.habitica.extensions.layoutInflater
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/FirstDropDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/FirstDropDialog.kt
index a9bb0a56c8..d09ed1b513 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/FirstDropDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/FirstDropDialog.kt
@@ -3,7 +3,7 @@ package com.habitrpg.android.habitica.ui.views.dialogs
import android.content.Context
import android.view.LayoutInflater
import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.views.PixelArtView
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt
index 6140962c39..f9de0e4e9d 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/dialogs/PetSuggestHatchDialog.kt
@@ -7,9 +7,13 @@ import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.lifecycleScope
+import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.DialogHatchPetButtonBinding
import com.habitrpg.android.habitica.databinding.DialogPetSuggestHatchBinding
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
import com.habitrpg.android.habitica.models.inventory.Animal
import com.habitrpg.android.habitica.models.inventory.Egg
@@ -52,6 +56,15 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
userViewModel = hiltEntryPoint.mainUserViewModel()
}
+ private var hasAllItems = false
+
+ override fun show() {
+ super.show()
+ if (!hasAllItems) {
+ Analytics.sendNavigationEvent("pet suggestion modal")
+ }
+ }
+
fun configure(
pet: Animal,
egg: Egg?,
@@ -121,6 +134,7 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
setTitle(R.string.hatch_pet_title)
}
addButton(R.string.close, false)
+ hasAllItems = true
} else {
if (hasMount) {
if (!hasEgg && !hasPotion) {
@@ -155,9 +169,10 @@ class PetSuggestHatchDialog(context: Context) : HabiticaAlertDialog(context) {
binding.currencyView.currency = "gems"
binding.currencyView.setTextColor(ContextCompat.getColor(context, R.color.white))
addButton(binding.root, true) { _, _ ->
- val activity = (getActivity() as? MainActivity) ?: return@addButton
+ val activity = (getActivity() as? MainActivity) ?: (HabiticaBaseApplication.getInstance(context)?.currentActivity?.get() as? MainActivity) ?: return@addButton
if ((userViewModel.user.value?.gemCount ?: hatchPrice) < hatchPrice) {
InsufficientGemsDialog(activity, hatchPrice).show()
+ Analytics.sendEvent("show insufficient gems modal", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("reason" to "pet suggest modal"))
return@addButton
}
val thisPotion = potion ?: return@addButton
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentItemRow.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentItemRow.kt
deleted file mode 100644
index 989186c5b3..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentItemRow.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.habitrpg.android.habitica.ui.views.equipment
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-import android.widget.LinearLayout
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.databinding.ItemImageRowBinding
-import com.habitrpg.common.habitica.extensions.layoutInflater
-import com.habitrpg.common.habitica.extensions.loadImage
-
-class EquipmentItemRow(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
-
- private val binding: ItemImageRowBinding = ItemImageRowBinding.inflate(context.layoutInflater, this)
- var equipmentIdentifier: String? = null
- set(value) {
- field = value
- val imageName = if (equipmentIdentifier?.isNotEmpty() == true && equipmentIdentifier?.endsWith("base_0") == false) "shop_$equipmentIdentifier" else "icon_head_0"
- binding.imageView.loadImage(imageName)
- }
-
- var customizationIdentifier: String? = null
- set(value) {
- field = value
- val imageName = if (customizationIdentifier?.isNotEmpty() == true) "icon_$customizationIdentifier" else "icon_head_0"
- binding.imageView.loadImage(imageName)
- }
-
- init {
- View.inflate(context, R.layout.item_image_row, this)
- isClickable = true
-
- val attributes = context.theme?.obtainStyledAttributes(
- attrs,
- R.styleable.EquipmentItemRow,
- 0,
- 0
- )
-
- binding.titleTextView.text = attributes?.getString(R.styleable.EquipmentItemRow_equipmentTitle)
- binding.valueTextView.visibility = View.GONE
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt
index 485a91e918..eb279bb5c3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/equipment/EquipmentOverviewView.kt
@@ -37,7 +37,7 @@ fun OverviewItem(
modifier: Modifier = Modifier,
isTwoHanded: Boolean = false
) {
- val hasIcon = isTwoHanded || (iconName?.isNotBlank() == true && iconName != "shirt_" && !iconName.endsWith("_none") && !iconName.endsWith("_base_0"))
+ val hasIcon = isTwoHanded || (iconName?.isNotBlank() == true && iconName != "shirt_" && !iconName.endsWith("_none") && !iconName.endsWith("_base_0") && !iconName.endsWith("_"))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt
index dc9418d102..5e6a47ab8e 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt
@@ -11,11 +11,10 @@ import com.google.firebase.analytics.FirebaseAnalytics
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.helpers.PurchaseTypes
import com.habitrpg.android.habitica.interactors.InsufficientGemsUseCase
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
@@ -37,9 +36,6 @@ class InsufficientGemsDialog(val parentActivity: Activity, var gemPrice: Int) :
@Inject
lateinit var configManager: AppConfigManager
- @Inject
- lateinit var analyticsManager: AnalyticsManager
-
@Inject
lateinit var purchaseHandler: PurchaseHandler
@@ -47,7 +43,6 @@ class InsufficientGemsDialog(val parentActivity: Activity, var gemPrice: Int) :
@InstallIn(SingletonComponent::class)
interface InsufficientGemsDialogEntryPoint {
fun configManager(): AppConfigManager
- fun analyticsManager(): AnalyticsManager
fun purchaseHandler(): PurchaseHandler
fun insufficientGemsUseCase(): InsufficientGemsUseCase
}
@@ -57,7 +52,6 @@ class InsufficientGemsDialog(val parentActivity: Activity, var gemPrice: Int) :
val hiltEntryPoint = EntryPointAccessors.fromApplication(parentActivity, InsufficientGemsDialogEntryPoint::class.java)
insufficientGemsUseCase = hiltEntryPoint.insufficientGemsUseCase()
configManager = hiltEntryPoint.configManager()
- analyticsManager = hiltEntryPoint.analyticsManager()
purchaseHandler = hiltEntryPoint.purchaseHandler()
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientHourglassesDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientHourglassesDialog.kt
index 115e0f181b..b63249fbd3 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientHourglassesDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientHourglassesDialog.kt
@@ -5,7 +5,7 @@ import android.os.Bundle
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.addCloseButton
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
class InsufficientHourglassesDialog(context: Context) : InsufficientCurrencyDialog(context) {
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt
index 23e68422d6..cc48d1d78c 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/BirthdayMenuView.kt
@@ -33,7 +33,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.getShortRemainingString
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.ui.views.PixelArtView
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/SubscriptionBuyGemsPromoView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/SubscriptionBuyGemsPromoView.kt
index fbd33c9609..9c05b4ff30 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/SubscriptionBuyGemsPromoView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/promo/SubscriptionBuyGemsPromoView.kt
@@ -7,7 +7,7 @@ import android.widget.RelativeLayout
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.extensions.inflate
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.common.habitica.extensions.getThemeColor
class SubscriptionBuyGemsPromoView @JvmOverloads constructor(
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
index 5932363fc6..3ab9210c12 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt
@@ -8,6 +8,7 @@ import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.os.bundleOf
@@ -17,16 +18,23 @@ import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
+import com.habitrpg.android.habitica.databinding.DialogPurchaseShopitemButtonBinding
+import com.habitrpg.android.habitica.databinding.DialogPurchaseShopitemHeaderBinding
import com.habitrpg.android.habitica.extensions.addCancelButton
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.extensions.getShortRemainingString
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.helpers.HitType
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.ArmoireActivityDirections
+import com.habitrpg.android.habitica.ui.fragments.purchases.EventOutcomeSubscriptionBottomSheetFragment
+import com.habitrpg.android.habitica.ui.fragments.purchases.SubscriptionBottomSheetFragment
import com.habitrpg.android.habitica.ui.views.CurrencyView
import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
@@ -37,6 +45,7 @@ import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientG
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientSubscriberGemsDialog
import com.habitrpg.android.habitica.ui.views.tasks.form.StepperValueFormView
+import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import com.habitrpg.common.habitica.helpers.launchCatching
import dagger.hilt.android.internal.managers.ViewComponentManager
@@ -57,11 +66,11 @@ class PurchaseDialog(
private val userRepository: UserRepository,
private val inventoryRepository: InventoryRepository,
val item: ShopItem,
- val parentActivity: Activity? = null
+ private val parentActivity: AppCompatActivity? = null
) : HabiticaAlertDialog(context) {
private val customHeader: View by lazy {
- LayoutInflater.from(context).inflate(R.layout.dialog_purchase_shopitem_header, null)
+ DialogPurchaseShopitemHeaderBinding.inflate(context.layoutInflater).root
}
private val currencyView: CurrencyViews
private val limitedTextView: TextView
@@ -75,8 +84,8 @@ class PurchaseDialog(
private var purchaseQuantity = 1
- var purchaseCardAction: ((ShopItem) -> Unit)? = null
- var onGearPurchased: ((ShopItem) -> Unit)? = null
+ private var purchaseCardAction: ((ShopItem) -> Unit)? = null
+ var onShopNeedsRefresh: ((ShopItem) -> Unit)? = null
private var shopItem: ShopItem = item
set(value) {
@@ -265,7 +274,7 @@ class PurchaseDialog(
pinTextView = customHeader.findViewById(R.id.pin_text)
addCloseButton()
- buyButton = addButton(layoutInflater.inflate(R.layout.dialog_purchase_shopitem_button, null), autoDismiss = false) { _, _ ->
+ buyButton = addButton(DialogPurchaseShopitemButtonBinding.inflate(layoutInflater).root, autoDismiss = false) { _, _ ->
onBuyButtonClicked()
}
priceLabel = buyButton.findViewById(R.id.priceLabel)
@@ -352,18 +361,30 @@ class PurchaseDialog(
when {
"gems" == shopItem.purchaseType -> {
if (shopItem.canAfford(user, purchaseQuantity)) {
- InsufficientSubscriberGemsDialog(context)
+ InsufficientSubscriberGemsDialog(context).show()
} else {
- InsufficientGoldDialog(context)
+ InsufficientGoldDialog(context).show()
}
}
- "gold" == shopItem.currency -> InsufficientGoldDialog(context)
+ "gold" == shopItem.currency -> InsufficientGoldDialog(context).show()
"gems" == shopItem.currency -> {
- parentActivity?.let { activity -> InsufficientGemsDialog(activity, shopItem.value) }
+ Analytics.sendEvent("show insufficient gems modal", EventCategory.BEHAVIOUR, HitType.EVENT, mapOf("reason" to "purchase modal", "item" to shopItem.key))
+ parentActivity?.let { activity -> InsufficientGemsDialog(activity, shopItem.value).show() }
}
- "hourglasses" == shopItem.currency -> InsufficientHourglassesDialog(context)
- else -> null
- }?.show()
+ "hourglasses" == shopItem.currency -> {
+ if (user?.isSubscribed == true) {
+ InsufficientHourglassesDialog(context).show()
+ } else {
+ val subscriptionBottomSheet = EventOutcomeSubscriptionBottomSheetFragment().apply {
+ eventType = EventOutcomeSubscriptionBottomSheetFragment.EVENT_HOURGLASS_SHOP_OPENED
+ }
+ parentActivity?.let { activity -> subscriptionBottomSheet.show(activity.supportFragmentManager, SubscriptionBottomSheetFragment.TAG) }
+ }
+
+ }
+
+
+ }
return
}
}
@@ -431,9 +452,9 @@ class PurchaseDialog(
}
}
val rightTextColor = when (item.currency) {
- "gold" -> ContextCompat.getColor(context, R.color.text_yellow)
- "gems" -> ContextCompat.getColor(context, R.color.text_green)
- "hourglasses" -> ContextCompat.getColor(context, R.color.text_brand)
+ "gold" -> ContextCompat.getColor(context, R.color.yellow_5)
+ "gems" -> ContextCompat.getColor(context, R.color.green_10)
+ "hourglasses" -> ContextCompat.getColor(context, R.color.brand_300)
else -> 0
}
val a = (application?.currentActivity?.get() ?: getActivity() ?: ownerActivity)
@@ -446,8 +467,8 @@ class PurchaseDialog(
)
inventoryRepository.retrieveInAppRewards()
userRepository.retrieveUser(forced = true)
- if (item.isTypeGear || item.currency == "hourglasses") {
- onGearPurchased?.invoke(item)
+ if (item.isTypeGear || item.currency == "hourglasses" || item.key == "gem") {
+ onShopNeedsRefresh?.invoke(item)
}
}
}
@@ -478,7 +499,10 @@ class PurchaseDialog(
val alert = HabiticaAlertDialog(context)
alert.setTitle(R.string.excess_items)
alert.setMessage(context.getString(R.string.excessItemsNoneLeft, item.text, purchaseQuantity, item.text))
- alert.addButton(context.getString(R.string.purchaseX, purchaseQuantity), true, false) { _, _ ->
+ alert.addButton(context.getString(R.string.purchaseX, purchaseQuantity),
+ isPrimary = true,
+ isDestructive = false
+ ) { _, _ ->
buyItem(purchaseQuantity)
}
alert.addCancelButton()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt
index 127cf26a86..9767dd4f42 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt
@@ -9,7 +9,7 @@ import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ChatBarViewBinding
import com.habitrpg.android.habitica.extensions.OnChangeTextWatcher
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.social.ChatMessage
import com.habitrpg.android.habitica.ui.helpers.AutocompleteAdapter
import com.habitrpg.android.habitica.ui.helpers.AutocompleteTokenizer
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/InvitationsView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/InvitationsView.kt
index bcf2a6bce4..12db2f6c0b 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/InvitationsView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/InvitationsView.kt
@@ -9,7 +9,9 @@ import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ViewInvitationBinding
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.android.habitica.extensions.flash
+import com.habitrpg.android.habitica.helpers.HapticFeedbackManager
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.invitations.GenericInvitation
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.common.habitica.extensions.layoutInflater
@@ -53,15 +55,21 @@ class InvitationsView @JvmOverloads constructor(
binding.root.setOnClickListener {
leaderID?.let { id ->
+ it.flash()
+ HapticFeedbackManager.tap(it)
val profileDirections = MainNavDirections.openProfileActivity(id)
MainNavigationController.navigate(profileDirections)
}
}
binding.acceptButton.setOnClickListener {
+ binding.root.flash()
+ HapticFeedbackManager.tap(it)
invitation.id?.let { it1 -> acceptCall?.invoke(it1) }
}
binding.rejectButton.setOnClickListener {
+ binding.root.flash()
+ HapticFeedbackManager.tap(it)
invitation.id?.let { it1 -> rejectCall?.invoke(it1) }
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/OldQuestProgressView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/OldQuestProgressView.kt
index c1aea3b32e..a2adaaedf6 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/OldQuestProgressView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/OldQuestProgressView.kt
@@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.models.inventory.QuestProgressCollect
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.views.HabiticaIcons
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
+import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
@@ -25,12 +26,6 @@ import com.habitrpg.common.habitica.extensions.loadImage
class OldQuestProgressView : LinearLayout {
private val binding = QuestProgressOldBinding.inflate(context.layoutInflater, this)
- private val rect = RectF()
- private val displayDensity = context.resources.displayMetrics.density
- private val lightGray = context.getThemeColor(R.attr.colorWindowBackground)
- private val mediumGray = ContextCompat.getColor(context, R.color.offset_background)
- private val darkGray = ContextCompat.getColor(context, R.color.separator)
-
constructor(context: Context) : super(context) {
setupView(context)
}
@@ -40,56 +35,34 @@ class OldQuestProgressView : LinearLayout {
}
private fun setupView(context: Context) {
- setWillNotDraw(false)
orientation = VERTICAL
-
+ binding.bossHealthView.valueSuffix = "HP"
+ binding.bossRageView.valueSuffix = context.getString(R.string.rage)
+ binding.bossHealthView
setScaledPadding(context, 16, 16, 16, 16)
-
- binding.bossHealthView.setSecondaryIcon(HabiticaIconsHelper.imageOfHeartLightBg())
- binding.bossHealthView.setDescriptionIcon(HabiticaIconsHelper.imageOfDamage())
- binding.bossRageView.setSecondaryIcon(HabiticaIconsHelper.imageOfRage())
- }
-
- override fun onDraw(canvas: Canvas?) {
- rect.set(
- 0.0f,
- 0.0f,
- (
- canvas?.width?.toFloat()
- ?: 1.0f
- ) / displayDensity,
- (
- canvas?.height?.toFloat()
- ?: 1.0f
- ) / displayDensity
- )
- canvas?.scale(displayDensity, displayDensity)
- HabiticaIcons.drawQuestBackground(canvas, rect, lightGray, darkGray, mediumGray)
- canvas?.scale(1.0f / displayDensity, 1.0f / displayDensity)
- super.onDraw(canvas)
}
fun setData(quest: QuestContent, progress: QuestProgress?) {
binding.collectionContainer.removeAllViews()
if (quest.isBossQuest) {
binding.bossNameView.text = quest.boss?.name
+ binding.bossHealthView.barHeight = 5.dpToPx(context)
if (progress != null) {
binding.bossHealthView.set(progress.hp, quest.boss?.hp?.toDouble() ?: 0.0)
}
if (quest.boss?.hasRage == true) {
- binding.bossRageView.visibility = View.VISIBLE
+ binding.bossRageWrapper.visibility = VISIBLE
+ binding.bossRageView.barHeight = 5.dpToPx(context)
+ binding.bossRageView
binding.bossRageView.set(progress?.rage ?: 0.0, quest.boss?.rage?.value ?: 0.0)
+ binding.bossRageNameView.text = quest.boss?.rage?.title
} else {
- binding.bossRageView.visibility = View.GONE
+ binding.bossRageWrapper.visibility = GONE
}
- binding.bossNameView.visibility = View.VISIBLE
- binding.bossHealthView.visibility = View.VISIBLE
- binding.collectedItemsNumberView.visibility = View.GONE
+ binding.bossHealthWrapper.visibility = VISIBLE
} else {
- binding.bossNameView.visibility = View.GONE
- binding.bossHealthView.visibility = View.GONE
- binding.bossRageView.visibility = View.GONE
- binding.collectedItemsNumberView.visibility = View.VISIBLE
+ binding.bossHealthWrapper.visibility = GONE
+ binding.bossRageWrapper.visibility = GONE
if (progress != null) {
val inflater = LayoutInflater.from(context)
@@ -99,24 +72,9 @@ class OldQuestProgressView : LinearLayout {
collectBinding.iconView.loadImage("quest_" + quest.key + "_" + collect.key)
collectBinding.nameView.text = contentCollect.text
collectBinding.valueView.set(collect.count.toDouble(), contentCollect.count.toDouble())
+ collectBinding.valueView.barHeight = 5.dpToPx(context)
}
}
}
}
-
- fun configure(user: User, userOnQuest: Boolean?) {
- val value = (user.party?.quest?.progress?.up ?: 0F).toDouble()
- val collectedItems = user.party?.quest?.progress?.collectedItems
- if (userOnQuest == true) {
- binding.bossHealthView.pendingValue = value
- binding.bossHealthView.description = context.getString(R.string.damage_pending, value)
- binding.bossHealthView.descriptionIconVisibility = View.VISIBLE
- binding.collectedItemsNumberView.text = context.getString(R.string.quest_items_found, collectedItems)
- } else {
- binding.bossHealthView.pendingValue = 0.0
- binding.bossHealthView.description = ""
- binding.bossHealthView.descriptionIconVisibility = View.GONE
- binding.collectedItemsNumberView.text = ""
- }
- }
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt
index 7c1e49ff05..9ffeae46e6 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/PartySeekingListItem.kt
@@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.MainNavDirections
import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.auth.LocalAuthentication
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.members.MemberFlags
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt
deleted file mode 100644
index 6d14ad9744..0000000000
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestMenuView.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.habitrpg.android.habitica.ui.views.social
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.core.content.ContextCompat
-import com.habitrpg.android.habitica.R
-import com.habitrpg.android.habitica.databinding.QuestMenuViewBinding
-import com.habitrpg.android.habitica.models.inventory.Quest
-import com.habitrpg.android.habitica.models.inventory.QuestContent
-import com.habitrpg.android.habitica.models.user.User
-import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
-import com.habitrpg.common.habitica.extensions.layoutInflater
-import java.util.Locale
-
-class QuestMenuView : LinearLayout {
- private val binding = QuestMenuViewBinding.inflate(context.layoutInflater, this)
-
- private var questContent: QuestContent? = null
-
- constructor(context: Context) : super(context) {
- setupView()
- }
-
- constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
- setupView()
- }
-
- private fun setupView() {
- orientation = VERTICAL
-
- binding.heartIconView.setImageBitmap(HabiticaIconsHelper.imageOfHeartDarkBg())
- binding.rageIconView.setImageBitmap(HabiticaIconsHelper.imageOfRage())
-
- binding.pendingDamageIconView.setImageBitmap(HabiticaIconsHelper.imageOfDamage())
- }
-
- fun configure(quest: Quest) {
- binding.healthBarView.currentValue = quest.progress?.hp ?: 0.0
- binding.rageBarView.currentValue = quest.progress?.rage ?: 0.0
- }
-
- fun configure(questContent: QuestContent) {
- this.questContent = questContent
- binding.healthBarView.maxValue = questContent.boss?.hp?.toDouble() ?: 0.0
- binding.bossNameView.text = questContent.boss?.name
- binding.typeTextView.text = context.getString(R.string.boss_quest)
-
- if (questContent.boss?.hasRage == true) {
- binding.rageView.visibility = View.VISIBLE
- binding.rageBarView.maxValue = questContent.boss?.rage?.value ?: 0.0
- } else {
- binding.rageView.visibility = View.GONE
- }
- }
-
- fun configure(user: User) {
- binding.pendingDamageTextView.text = String.format(Locale.getDefault(), "%.01f", (user.party?.quest?.progress?.up ?: 0f))
- }
-
- fun hideBossArt() {
- binding.topView.orientation = HORIZONTAL
- binding.topView.setBackgroundColor(questContent?.colors?.mediumColor ?: 0)
- binding.bossNameView.gravity = Gravity.START
- binding.bossNameView.layoutParams = LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1F)
- binding.typeTextView.setTextColor(questContent?.colors?.extraLightColor ?: 0)
- }
-
- fun showBossArt() {
- binding.topView.orientation = VERTICAL
- binding.topView.setBackgroundColor(ContextCompat.getColor(context, R.color.transparent))
- binding.bossNameView.gravity = Gravity.END
- binding.bossNameView.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- binding.typeTextView.setTextColor(ContextCompat.getColor(context, R.color.white))
- }
-}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt
index 5ea1ee0875..d6b43a1925 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/QuestProgressView.kt
@@ -91,14 +91,14 @@ class QuestProgressView : LinearLayout {
}
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
if (quest?.isValid == true) {
val colors = quest?.colors
if (colors != null) {
rect.set(0.0f, 0.0f, width.toFloat() / displayDensity, height.toFloat() / displayDensity)
- canvas?.scale(displayDensity, displayDensity)
+ canvas.scale(displayDensity, displayDensity)
HabiticaIcons.drawQuestBackground(canvas, rect, colors.darkColor, colors.mediumColor, colors.extraLightColor)
- canvas?.scale(1.0f / displayDensity, 1.0f / displayDensity)
+ canvas.scale(1.0f / displayDensity, 1.0f / displayDensity)
}
}
super.onDraw(canvas)
@@ -256,7 +256,6 @@ class QuestProgressView : LinearLayout {
private fun getLocationName(key: String): String {
return when (key) {
"market" -> context.getString(R.string.market)
- "tavern" -> context.getString(R.string.sidebar_tavern)
"questShop" -> context.getString(R.string.questShop)
"seasonalShop" -> context.getString(R.string.seasonalShop)
"stable" -> context.getString(R.string.sidebar_stable)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/MountBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/MountBottomSheet.kt
new file mode 100644
index 0000000000..555bba7b3d
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/MountBottomSheet.kt
@@ -0,0 +1,202 @@
+package com.habitrpg.android.habitica.ui.views.stable
+
+import com.habitrpg.android.habitica.models.inventory.Animal
+import com.habitrpg.android.habitica.models.inventory.Mount
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.StartOffset
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.keyframes
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.imageResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.zIndex
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.interactors.ShareMountUseCase
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.android.habitica.ui.views.BackgroundScene
+import com.habitrpg.android.habitica.ui.views.HabiticaButton
+import com.habitrpg.common.habitica.helpers.launchCatching
+import kotlinx.coroutines.MainScope
+import java.util.Calendar
+import kotlin.math.sin
+
+@Composable
+private fun getBackgroundPainter(): ImageBitmap {
+ val calendar = Calendar.getInstance()
+ val month = calendar.get(Calendar.MONTH)
+ return ImageBitmap.imageResource(
+ when (month) {
+ Calendar.JANUARY -> R.drawable.stable_tile_janurary
+ Calendar.FEBRUARY -> R.drawable.stable_tile_february
+ Calendar.MARCH -> R.drawable.stable_tile_march
+ Calendar.APRIL -> R.drawable.stable_tile_april
+ Calendar.MAY -> R.drawable.stable_tile_may
+ Calendar.JUNE -> R.drawable.stable_tile_june
+ Calendar.JULY -> R.drawable.stable_tile_july
+ Calendar.AUGUST -> R.drawable.stable_tile_august
+ Calendar.SEPTEMBER -> R.drawable.stable_tile_september
+ Calendar.OCTOBER -> R.drawable.stable_tile_october
+ Calendar.NOVEMBER -> R.drawable.stable_tile_november
+ Calendar.DECEMBER -> R.drawable.stable_tile_december
+ else -> R.drawable.stable_tile_may
+ }
+ )
+}
+
+@Composable
+fun MountBottomSheet(
+ mount: Mount,
+ isCurrentMount: Boolean,
+ onEquip: ((String) -> Unit)?,
+ onDismiss: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val infiniteTransition = rememberInfiniteTransition()
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier.padding(horizontal = 22.dp)
+ ) {
+ Text(
+ mount.text ?: "",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = HabiticaTheme.colors.textTertiary
+ )
+ Box(
+ modifier = Modifier
+ .padding(top = 9.dp, bottom = 16.dp)
+ .fillMaxWidth()
+ .height(124.dp)
+ .clip(HabiticaTheme.shapes.medium)
+ ) {
+ BackgroundScene()
+
+ val regularPosition = 12f
+ val highJump = 0f
+ val lowJump = 8f
+ val position by if (isAnimalFlying(mount)) {
+ infiniteTransition.animateFloat(
+ initialValue = 4f,
+ targetValue = 0f,
+ animationSpec = infiniteRepeatable(
+ tween(
+ 2500,
+ easing = CubicBezierEasing(0.3f, 0.0f, 0.2f, 1.0f)
+ ), RepeatMode.Reverse
+ ),
+ label = "animalPosition"
+ )
+ } else {
+ infiniteTransition.animateFloat(
+ initialValue = regularPosition,
+ targetValue = highJump,
+ animationSpec = infiniteRepeatable(animation = keyframes {
+ durationMillis = 6000
+ regularPosition at 0 with LinearOutSlowInEasing
+ highJump at 150 with LinearOutSlowInEasing
+ regularPosition at 300 with FastOutSlowInEasing
+ regularPosition at 1800 with FastOutSlowInEasing
+ lowJump at 1850 with LinearOutSlowInEasing
+ regularPosition at 1900 with LinearOutSlowInEasing
+ regularPosition at 2100 with FastOutSlowInEasing
+ lowJump at 2200 with LinearOutSlowInEasing
+ regularPosition at 2350 with LinearOutSlowInEasing
+ regularPosition at 6000
+ }, RepeatMode.Restart, StartOffset(1500)), label = "animalPosition"
+ )
+ }
+ MountView(mount, modifier = Modifier
+ .offset(0.dp, position.dp)
+ .size(81.dp, 99.dp)
+ .align(Alignment.TopCenter)
+ .zIndex(2f)
+ )
+ }
+ val context = LocalContext.current
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ contentPadding = PaddingValues(12.dp),
+ modifier = Modifier.padding(bottom = 16.dp),
+ onClick = {
+ MainScope().launchCatching {
+ ShareMountUseCase().callInteractor(
+ ShareMountUseCase.RequestValues(
+ mount.key,
+ "",
+ context
+ ))
+ }
+ onDismiss()
+ }) {
+ Text(stringResource(id = R.string.share))
+ }
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ contentPadding = PaddingValues(12.dp),
+ onClick = {
+ onEquip?.invoke(mount.key)
+ onDismiss()
+ }) {
+ if (isCurrentMount) {
+ Text(stringResource(id = R.string.unequip))
+ } else {
+ Text(stringResource(id = R.string.equip))
+ }
+ }
+ }
+}
+
+fun isAnimalFlying(animal: Animal): Boolean {
+ if (listOf(
+ "FlyingPig",
+ "Bee"
+ ).contains(animal.animal)
+ ) return true
+ return listOf(
+ "Ghost",
+ "Cupid",
+ "Fairy",
+ "SolarSystem",
+ "Vampire"
+ ).contains(animal.color)
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/MountView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/MountView.kt
new file mode 100644
index 0000000000..cc669d2a9b
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/MountView.kt
@@ -0,0 +1,56 @@
+package com.habitrpg.android.habitica.ui.views.stable
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.viewinterop.AndroidView
+import com.habitrpg.android.habitica.models.inventory.Mount
+import com.habitrpg.common.habitica.extensions.loadImage
+import com.habitrpg.common.habitica.views.PixelArtView
+
+class MountView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : FrameLayout(context, attrs) {
+
+ val hasLoadedImages: Boolean
+ get() {
+ return bodyView.bitmap != null && headView.bitmap != null
+ }
+ private val bodyView: PixelArtView = PixelArtView(context)
+ private val headView: PixelArtView = PixelArtView(context)
+
+ fun setMount(key: String) {
+ bodyView.loadImage("Mount_Body_$key")
+ headView.loadImage("Mount_Head_$key")
+ }
+
+ init {
+ addView(bodyView)
+ bodyView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ addView(headView)
+ headView.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ }
+
+}
+
+@Composable
+fun MountView(mount: Mount, modifier: Modifier = Modifier) {
+ MountView(mount.key, modifier)
+}
+
+@Composable
+fun MountView(mountKey: String, modifier: Modifier = Modifier) {
+ AndroidView(
+ modifier = modifier,
+ factory = { context ->
+ MountView(context)
+ },
+ update = { view ->
+ view.setMount(mountKey)
+ }
+ )
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/PetBottomSheet.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/PetBottomSheet.kt
new file mode 100644
index 0000000000..c9a32f316b
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/stable/PetBottomSheet.kt
@@ -0,0 +1,365 @@
+package com.habitrpg.android.habitica.ui.views.stable
+
+import android.graphics.Bitmap
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.StartOffset
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.keyframes
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.scaleIn
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.ImageShader
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.TileMode
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.imageResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.zIndex
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.interactors.ShareMountUseCase
+import com.habitrpg.android.habitica.interactors.SharePetUseCase
+import com.habitrpg.android.habitica.models.inventory.Food
+import com.habitrpg.android.habitica.models.inventory.Pet
+import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
+import com.habitrpg.android.habitica.ui.views.BackgroundScene
+import com.habitrpg.android.habitica.ui.views.HabiticaButton
+import com.habitrpg.android.habitica.ui.views.PixelArtView
+import com.habitrpg.common.habitica.extensions.getThemeColor
+import com.habitrpg.common.habitica.helpers.MainNavigationController
+import com.habitrpg.common.habitica.helpers.launchCatching
+import com.habitrpg.shared.habitica.models.responses.FeedResponse
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.delay
+import kotlin.math.sin
+
+@Composable
+private fun getFoodPainter(petColor: String): ImageBitmap {
+ return ImageBitmap.imageResource(
+ when (petColor) {
+ "Base" -> R.drawable.feed_base
+ "CottonCandyBlue" -> R.drawable.feed_blue
+ "Desert" -> R.drawable.feed_desert
+ "Golden" -> R.drawable.feed_golden
+ "CottonCandyPink" -> R.drawable.feed_pink
+ "Red" -> R.drawable.feed_red
+ "Shade" -> R.drawable.feed_shade
+ "Skeleton" -> R.drawable.feed_skeleton
+ "White" -> R.drawable.feed_white
+ "Zombie" -> R.drawable.feed_zombie
+ else -> R.drawable.feed_base
+ }
+ )
+}
+
+@Composable
+fun PetBottomSheet(
+ pet: Pet,
+ trained: Int,
+ isCurrentPet: Boolean,
+ canRaiseToMount: Boolean,
+ ownsSaddles: Boolean,
+ onEquip: ((String) -> Unit)?,
+ onFeed: (suspend (Pet, Food?) -> FeedResponse?)?,
+ onDismiss: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ val infiniteTransition = rememberInfiniteTransition()
+ val coroutineScope = rememberCoroutineScope()
+
+ var oldFeedValue: Int by remember { mutableIntStateOf(0) }
+ var feedValue: Int by remember { mutableIntStateOf(0) }
+ var feedMessage: String by remember { mutableStateOf("") }
+ var showFeedResponse: Boolean by remember { mutableStateOf(false) }
+
+ LaunchedEffect(key1 = pet, block = {
+ feedValue = trained
+ })
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier.padding(horizontal = 22.dp)
+ ) {
+ Text(
+ pet.text ?: "",
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Medium,
+ color = HabiticaTheme.colors.textTertiary
+ )
+ Box(
+ modifier = Modifier
+ .padding(top = 9.dp, bottom = 16.dp)
+ .fillMaxWidth()
+ .height(124.dp)
+ .clip(HabiticaTheme.shapes.medium)
+ ) {
+ BackgroundScene()
+
+ this@Column.AnimatedVisibility(
+ visible = showFeedResponse, modifier = Modifier
+ .offset(y = 90.dp)
+ .align(Alignment.TopCenter)
+ .zIndex(4f), enter = fadeIn(), exit = fadeOut()
+ ) {
+ Text(
+ feedMessage,
+ color = HabiticaTheme.colors.textPrimary,
+ fontSize = 12.sp,
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .background(
+ HabiticaTheme.colors.windowBackground,
+ HabiticaTheme.shapes.medium
+ )
+ .padding(8.dp, 3.dp)
+ .alpha(0.65f)
+ )
+ }
+
+ this@Column.AnimatedVisibility(
+ visible = showFeedResponse,
+ modifier = Modifier
+ .offset(y = 6.dp)
+ .align(Alignment.TopCenter)
+ .zIndex(4f), enter = fadeIn() + scaleIn(), exit = fadeOut()
+ ) {
+ val progressAnimation = animateFloatAsState(
+ targetValue = feedValue / 50f,
+ animationSpec = tween(durationMillis = 400, easing = FastOutSlowInEasing)
+ )
+ val scale = (sin((progressAnimation.value - oldFeedValue) * 120) * 0.02f)
+ LinearProgressIndicator(
+ progress = progressAnimation.value,
+ color = HabiticaTheme.colors.successColor,
+ strokeCap = StrokeCap.Round,
+ modifier = Modifier
+ .width(200.dp)
+ .scale(1.0f + scale)
+ .background(
+ HabiticaTheme.colors.windowBackground,
+ HabiticaTheme.shapes.medium
+ )
+ .padding(3.dp)
+ )
+ }
+
+ val regularPosition = 44f
+ val highJump = 32f
+ val midJump = 37f
+ val lowJump = 40f
+ val position by if (showFeedResponse) {
+ infiniteTransition.animateFloat(
+ initialValue = regularPosition,
+ targetValue = highJump,
+ animationSpec = infiniteRepeatable(animation = keyframes {
+ durationMillis = 800
+ regularPosition at 0 with FastOutSlowInEasing
+ lowJump at 50 with LinearOutSlowInEasing
+ regularPosition at 100 with LinearOutSlowInEasing
+ regularPosition at 300 with FastOutSlowInEasing
+ midJump at 400 with LinearOutSlowInEasing
+ regularPosition at 550 with LinearOutSlowInEasing
+ regularPosition at 800
+ }, RepeatMode.Restart, StartOffset(1500)), label = "animalPosition"
+ )
+ } else if (isAnimalFlying(pet)) {
+ infiniteTransition.animateFloat(
+ initialValue = 24f,
+ targetValue = 16f,
+ animationSpec = infiniteRepeatable(
+ tween(
+ 2500,
+ easing = CubicBezierEasing(0.3f, 0.0f, 0.2f, 1.0f)
+ ), RepeatMode.Reverse
+ ),
+ label = "animalPosition"
+ )
+ } else {
+ infiniteTransition.animateFloat(
+ initialValue = regularPosition,
+ targetValue = highJump,
+ animationSpec = infiniteRepeatable(animation = keyframes {
+ durationMillis = 6000
+ regularPosition at 0 with LinearOutSlowInEasing
+ highJump at 150 with LinearOutSlowInEasing
+ regularPosition at 300 with FastOutSlowInEasing
+ regularPosition at 1800 with FastOutSlowInEasing
+ lowJump at 1850 with LinearOutSlowInEasing
+ regularPosition at 1900 with LinearOutSlowInEasing
+ regularPosition at 2100 with FastOutSlowInEasing
+ lowJump at 2200 with LinearOutSlowInEasing
+ regularPosition at 2350 with LinearOutSlowInEasing
+ regularPosition at 6000
+ }, RepeatMode.Restart, StartOffset(1500)), label = "animalPosition"
+ )
+ }
+ PixelArtView(
+ imageName = "stable_Pet-${pet.animal}-${pet.color}", modifier = Modifier
+ .offset(0.dp, position.dp)
+ .size(68.dp)
+ .align(Alignment.TopCenter)
+ .zIndex(2f)
+ )
+ }
+ if (canRaiseToMount) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.padding(bottom = 16.dp)
+ ) {
+ HabiticaButton(
+ Color(LocalContext.current.getThemeColor(R.attr.colorTintedBackgroundOffset)),
+ HabiticaTheme.colors.textPrimary,
+ onClick = {
+ if (ownsSaddles) {
+ val saddle = Food()
+ saddle.key = "Saddle"
+ coroutineScope.launchCatching {
+ onFeed?.invoke(pet, saddle)
+ }
+ } else {
+ MainNavigationController.navigate(R.id.marketFragment)
+ }
+ onDismiss()
+ }, modifier = Modifier
+ .weight(1.0f)
+ .heightIn(min = 101.dp)
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ PixelArtView(
+ ImageBitmap.imageResource(R.drawable.feed_saddle),
+ modifier = Modifier.size(64.dp, 50.dp)
+ )
+ Text(stringResource(id = R.string.use_saddle))
+ }
+ }
+ HabiticaButton(
+ Color(LocalContext.current.getThemeColor(R.attr.colorTintedBackgroundOffset)),
+ HabiticaTheme.colors.textPrimary,
+ onClick = {
+ coroutineScope.launchCatching {
+ val response = onFeed?.invoke(pet, null)
+ feedMessage = response?.message ?: ""
+ showFeedResponse = true
+ delay(700)
+ oldFeedValue = feedValue
+ feedValue = if (response?.value == -1) 50 else (response?.value ?: feedValue)
+
+ delay(1800)
+ showFeedResponse = false
+ if (response?.value == -1) {
+ onDismiss()
+ }
+ }
+ }, modifier = Modifier
+ .weight(1.0f)
+ .heightIn(min = 101.dp)
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ PixelArtView(
+ getFoodPainter(pet.color),
+ modifier = Modifier.size(64.dp, 50.dp)
+ )
+ Text(stringResource(id = R.string.feed))
+ }
+ }
+ }
+ }
+ val context = LocalContext.current
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ contentPadding = PaddingValues(12.dp),
+ modifier = Modifier.padding(bottom = 16.dp),
+ onClick = {
+ MainScope().launchCatching {
+ SharePetUseCase().callInteractor(
+ SharePetUseCase.RequestValues(
+ pet.key,
+ "",
+ context
+ ))
+ }
+ onDismiss()
+ }) {
+ Text(stringResource(id = R.string.share))
+ }
+ HabiticaButton(
+ background = HabiticaTheme.colors.tintedUiSub,
+ color = Color.White,
+ contentPadding = PaddingValues(12.dp),
+ onClick = {
+ onEquip?.invoke(pet.key)
+ onDismiss()
+ }) {
+ if (isCurrentPet) {
+ Text(stringResource(id = R.string.unequip))
+ } else {
+ Text(stringResource(id = R.string.equip))
+ }
+ }
+ }
+}
+
+fun isAnimalFlying(pet: Pet): Boolean {
+ if (listOf(
+ "FlyingPig",
+ "Bee"
+ ).contains(pet.animal)
+ ) return true
+ return listOf(
+ "Ghost",
+ "Cupid",
+ "Fairy",
+ "SolarSystem",
+ "Vampire"
+ ).contains(pet.color)
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriberBenefitView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriberBenefitView.kt
new file mode 100644
index 0000000000..fe6022458b
--- /dev/null
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriberBenefitView.kt
@@ -0,0 +1,81 @@
+package com.habitrpg.android.habitica.ui.views.subscriptions
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.data.InventoryRepository
+import com.habitrpg.android.habitica.databinding.SubscriptionBenefitsBinding
+import com.habitrpg.android.habitica.helpers.AppConfigManager
+import com.habitrpg.android.habitica.helpers.PurchaseHandler
+import com.habitrpg.android.habitica.interactors.InsufficientGemsUseCase
+import com.habitrpg.common.habitica.extensions.layoutInflater
+import com.habitrpg.common.habitica.extensions.loadImage
+import com.habitrpg.common.habitica.helpers.launchCatching
+import dagger.hilt.EntryPoint
+import dagger.hilt.InstallIn
+import dagger.hilt.android.EntryPointAccessors
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.flow.firstOrNull
+import javax.inject.Inject
+
+class SubscriberBenefitView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null
+) : LinearLayout(context, attrs) {
+ private val binding: SubscriptionBenefitsBinding
+
+ @Inject
+ lateinit var configManager: AppConfigManager
+ @Inject
+ lateinit var inventoryRepository: InventoryRepository
+
+ @EntryPoint
+ @InstallIn(SingletonComponent::class)
+ interface ThisEntryPoint {
+ fun configManager(): AppConfigManager
+ fun inventoryRepository(): InventoryRepository
+ }
+
+ init {
+ binding = SubscriptionBenefitsBinding.inflate(context.layoutInflater, this)
+ orientation = VERTICAL
+ val hiltEntryPoint = EntryPointAccessors.fromApplication(context, ThisEntryPoint::class.java)
+ configManager = hiltEntryPoint.configManager()
+ inventoryRepository = hiltEntryPoint.inventoryRepository()
+
+ MainScope().launchCatching {
+ val item = inventoryRepository.getLatestMysteryItem().firstOrNull()
+ binding.subBenefitsMysteryItemIcon.loadImage(
+ "shop_set_mystery_${
+ item?.key?.split(
+ "_"
+ )?.last()
+ }"
+ )
+ binding.subBenefitsMysteryItemText.text =
+ context.getString(R.string.subscribe_listitem3_description_new, item?.text)
+ }
+
+ binding.benefitArmoireWrapper.isVisible = configManager.enableArmoireSubs()
+ binding.benefitFaintWrapper.isVisible = configManager.enableFaintSubs()
+ }
+
+ fun hideDeathBenefit() {
+ binding.benefitFaintWrapper.isVisible = false
+ }
+
+ fun hideArmoireBenefit() {
+ binding.benefitArmoireWrapper.isVisible = false
+ }
+
+ fun hideGemsForGoldBenefit() {
+ binding.benefitGemsForGoldWrapper.isVisible = false
+ }
+
+ fun hideMysticHourglassBenefit() {
+ binding.benefitHourglassesWrapper.isVisible = false
+ }
+}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionOptionView.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionOptionView.kt
index a4cea8a244..5d3afd03a1 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionOptionView.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/subscriptions/SubscriptionOptionView.kt
@@ -32,11 +32,11 @@ class SubscriptionOptionView(context: Context, attrs: AttributeSet) : FrameLayou
binding.gemCapTextView.text = a.getText(R.styleable.SubscriptionOptionView_gemCapText)
setFlagText(a.getText(R.styleable.SubscriptionOptionView_flagText))
val hourGlassCount = a.getInteger(R.styleable.SubscriptionOptionView_hourGlassCount, 0)
+ binding.hourglassTextView.visibility = View.VISIBLE
if (hourGlassCount != 0) {
binding.hourglassTextView.text = context.getString(R.string.subscription_hourglasses, hourGlassCount)
- binding.hourglassTextView.visibility = View.VISIBLE
} else {
- binding.hourglassTextView.visibility = View.GONE
+ binding.hourglassTextView.text = context.getString(R.string.subscription_hourglasses_3month_timeframe)
}
}
@@ -70,7 +70,7 @@ class SubscriptionOptionView(context: Context, attrs: AttributeSet) : FrameLayou
binding.priceLabel.setTextColor(ContextCompat.getColor(context, R.color.text_brand))
binding.descriptionTextView.setTextColor(ContextCompat.getColor(context, R.color.text_brand))
} else {
- binding.wrapper.setBackgroundResource(R.drawable.subscription_box_bg)
+ binding.wrapper.setBackgroundResource(R.drawable.subscription_type_box_bg)
binding.subscriptionSelectedView.setBackgroundResource(R.drawable.subscription_unselected)
binding.gemCapTextView.setBackgroundResource(R.drawable.pill_bg_gray)
binding.gemCapTextView.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
index eb77a04e8d..e5e1ad9592 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/TaskFilterDialog.kt
@@ -3,19 +3,20 @@ package com.habitrpg.android.habitica.ui.views.tasks
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
+import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.WindowManager
-import android.widget.Button
import android.widget.RadioGroup
+import android.widget.TextView
import androidx.annotation.IdRes
import androidx.appcompat.widget.AppCompatCheckBox
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.widget.CompoundButtonCompat
-import androidx.core.widget.TextViewCompat
import androidx.lifecycle.lifecycleScope
+import com.google.android.material.button.MaterialButton
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TagRepository
import com.habitrpg.android.habitica.databinding.DialogTaskFilterBinding
@@ -68,7 +69,7 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
private val deletedTags = ArrayList()
private val addIcon: Drawable?
- private var isEditing: Boolean = false
+ private var isEditingTags: Boolean = false
init {
addIcon = ContextCompat.getDrawable(context, R.drawable.ic_add_purple_300_36dp)
@@ -88,7 +89,7 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
}
binding.clearButton.setOnClickListener {
- if (isEditing) {
+ if (isEditingTags) {
stopEditing()
}
setActiveFilter(null)
@@ -116,7 +117,7 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
this.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
}
- fun setTags(tags: List) {
+ private fun setTags(tags: List) {
this.tags = repository.getUnmanagedCopy(tags).toMutableList()
createTagViews()
}
@@ -135,47 +136,59 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
)
val leftPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12f, context.resources.displayMetrics).toInt()
val verticalPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, context.resources.displayMetrics).toInt()
+ sortTagPositions()
for (tag in tags) {
- val tagCheckbox = AppCompatCheckBox(context)
- tagCheckbox.text = tag.name
- tagCheckbox.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
- tagCheckbox.isChecked = viewModel.tags.contains(tag.id)
- tagCheckbox.setPadding(
- tagCheckbox.paddingLeft + leftPadding,
- verticalPadding,
- tagCheckbox.paddingRight,
- verticalPadding
- )
- tagCheckbox.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
- CompoundButtonCompat.setButtonTintList(tagCheckbox, colorStateList)
- tagCheckbox.setOnCheckedChangeListener { _, isChecked ->
- if (isChecked) {
- viewModel.addActiveTag(tag.id)
- } else {
- viewModel.removeActiveTag(tag.id)
+ if (tag.id.isBlank()) {
+ // Title for tag group
+ val view = TextView(context)
+ view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
+ view.text = tag.name
+ view.setTextColor(context.getThemeColor(R.attr.textColorTintedPrimary))
+ binding.tagsList.addView(view)
+ } else {
+ val tagCheckbox = AppCompatCheckBox(context)
+ tagCheckbox.text = tag.name
+ tagCheckbox.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)
+ tagCheckbox.isChecked = viewModel.tags.contains(tag.id)
+ tagCheckbox.setPadding(
+ tagCheckbox.paddingLeft + leftPadding,
+ verticalPadding,
+ tagCheckbox.paddingRight,
+ verticalPadding
+ )
+ tagCheckbox.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
+ CompoundButtonCompat.setButtonTintList(tagCheckbox, colorStateList)
+ tagCheckbox.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) {
+ viewModel.addActiveTag(tag.id)
+ } else {
+ viewModel.removeActiveTag(tag.id)
+ }
+ filtersChanged()
}
- filtersChanged()
+ binding.tagsList.addView(tagCheckbox)
}
- binding.tagsList.addView(tagCheckbox)
}
createAddTagButton()
}
private fun createAddTagButton() {
- val button = Button(context)
+ val button = MaterialButton(context)
button.setText(R.string.add_tag)
- button.setOnClickListener { createTag() }
- button.setCompoundDrawablesWithIntrinsicBounds(addIcon, null, null, null)
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
- TextViewCompat.setCompoundDrawableTintList(button, ColorStateList.valueOf(context.getThemeColor(R.attr.colorAccent)))
- }
+ button.icon = addIcon
+ button.iconTint = ColorStateList.valueOf(context.getThemeColor(R.attr.colorAccent))
+ button.iconGravity = MaterialButton.ICON_GRAVITY_START
button.elevation = 0f
- button.setBackgroundResource(R.drawable.button_background_gray_700)
- button.setShadowLayer(0f, 0f, 0f, ContextCompat.getColor(context, R.color.content_background))
+ button.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(context, R.color.gray700_gray10))
+ button.setStrokeColorResource(R.color.content_background)
+ button.strokeWidth = 0
button.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
+ button.setOnClickListener { createTag() }
+
binding.tagsList.addView(button)
}
+
private fun createTag() {
val tag = Tag()
tag.id = UUID.randomUUID().toString()
@@ -185,7 +198,7 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
}
private fun startEditing() {
- isEditing = true
+ isEditingTags = true
binding.tagsList.removeAllViews()
createTagEditViews()
binding.tagEditButton.setText(R.string.done)
@@ -193,14 +206,28 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
}
private fun stopEditing() {
- isEditing = false
+ // Refresh Tags
+ setActiveTags(null)
+ // Filter out tags with empty names
+ val emptyTagsToRemove = createdTags.values.filter { it.name.isBlank() }
+ createdTags.values.removeAll(emptyTagsToRemove.toSet())
+ tags.removeAll(emptyTagsToRemove)
+
+
+ isEditingTags = false
binding.tagsList.removeAllViews()
createTagViews()
binding.tagEditButton.setText(R.string.edit_tag_btn_edit)
this.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
lifecycleScope.launchCatching {
- repository.updateTags(editedTags.values).forEach { editedTags.remove(it.id) }
- repository.createTags(createdTags.values).forEach { tag -> createdTags.remove(tag.id) }
+ repository.updateTags(editedTags.values).forEach { tag ->
+ editedTags.remove(tag.id)
+ tags.remove(tag)
+ }
+ repository.createTags(createdTags.values).forEach { tag ->
+ createdTags.remove(tag.id)
+ tags.remove(tag)
+ }
repeat(repository.deleteTags(deletedTags).size) { deletedTags.clear() }
}
}
@@ -215,39 +242,61 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
}
private fun createTagEditView(inflater: LayoutInflater, index: Int, tag: Tag) {
- val editBinding = EditTagItemBinding.inflate(inflater, binding.tagsList, false)
- editBinding.editText.setText(tag.name)
- editBinding.editText.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
- editBinding.editText.addTextChangedListener(
- OnChangeTextWatcher { s, _, _, _ ->
- if (index >= tags.size) {
- return@OnChangeTextWatcher
- }
- val changedTag = tags[index]
- changedTag.name = s.toString()
- if (createdTags.containsKey(changedTag.id)) {
- createdTags[changedTag.id] = changedTag
- } else {
- editedTags[changedTag.id] = changedTag
+ if (tag.id.isBlank()) {
+ // This is a title tag ("Challenge", "Group", "Your Tags", etc)
+ val view = TextView(context)
+ view.text = tag.name
+ view.setTypeface(view.typeface, Typeface.BOLD)
+ view.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
+ binding.tagsList.addView(view)
+ } else {
+ if (tag.group != null) {
+ // Make Group tags uneditable
+ val editBinding = EditTagItemBinding.inflate(inflater, binding.tagsList, false)
+ editBinding.editText.setText(tag.name)
+ editBinding.editText.isEnabled = false
+ editBinding.editText.setTextColor(ContextCompat.getColor(context, R.color.disabled_background))
+ editBinding.deleteButton.isEnabled = false
+ editBinding.deleteButton.alpha = .50f
+ binding.tagsList.addView(editBinding.root)
+ } else {
+ // All tags (except group tags) are editable
+ val editBinding = EditTagItemBinding.inflate(inflater, binding.tagsList, false)
+ editBinding.editText.setText(tag.name)
+ editBinding.editText.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
+ editBinding.editText.addTextChangedListener(
+ OnChangeTextWatcher { s, _, _, _ ->
+ if (index >= tags.size) {
+ return@OnChangeTextWatcher
+ }
+ val changedTag = tags[index]
+ changedTag.name = s.toString()
+ if (createdTags.containsKey(changedTag.id)) {
+ createdTags[changedTag.id] = changedTag
+ } else {
+ editedTags[changedTag.id] = changedTag
+ }
+ tags[index] = changedTag
+ }
+ )
+ editBinding.deleteButton.setOnClickListener {
+ deletedTags.add(tag.id)
+ if (createdTags.containsKey(tag.id)) {
+ createdTags.remove(tag.id)
+ }
+ if (editedTags.containsKey(tag.id)) {
+ editedTags.remove(tag.id)
+ }
+ viewModel.tags.remove(tag.id)
+ tags.remove(tag)
+ binding.tagsList.removeView(editBinding.root)
}
- tags[index] = changedTag
+ binding.tagsList.addView(editBinding.root)
}
- )
- editBinding.deleteButton.setOnClickListener {
- deletedTags.add(tag.id)
- if (createdTags.containsKey(tag.id)) {
- createdTags.remove(tag.id)
- }
- if (editedTags.containsKey(tag.id)) {
- editedTags.remove(tag.id)
- }
- viewModel.tags.remove(tag.id)
- tags.remove(tag)
- binding.tagsList.removeView(editBinding.root)
}
- binding.tagsList.addView(editBinding.root)
}
+
private fun setActiveTags(tagIds: MutableList?) {
if (tagIds == null) {
this.viewModel.tags.clear()
@@ -320,9 +369,54 @@ class TaskFilterDialog(context: Context, private val repository: TagRepository,
filtersChanged()
}
+ private fun sortTagPositions() {
+ val sortedTagList = arrayListOf()
+ val challengeTagList = arrayListOf()
+ val groupTagList = arrayListOf()
+ val otherTagList = arrayListOf()
+
+ val challengesTagTitleName = context.getString(R.string.challenge_tags)
+ val groupsTagTitleName = context.getString(R.string.group_tags)
+ val otherTagTitleName = context.getString(R.string.your_tags)
+
+ tags.forEach {
+ if (it.name == challengesTagTitleName || it.name == groupsTagTitleName || it.name == otherTagTitleName) {
+ // This tag is a title, skip it.
+ return@forEach
+ }
+ if (it.challenge) {
+ challengeTagList.add(it)
+ } else if (it.group != null) {
+ groupTagList.add(it)
+ } else {
+ otherTagList.add(it)
+ }
+ }
+
+ val challengesTagTitle = Tag().apply { name = challengesTagTitleName }
+ val groupsTagTitle = Tag().apply { name = groupsTagTitleName }
+ val otherTagTitle = Tag().apply { name = otherTagTitleName }
+
+ if (challengeTagList.isNotEmpty() && sortedTagList.none { it.name == challengesTagTitleName }) {
+ sortedTagList.add(challengesTagTitle)
+ sortedTagList.addAll(challengeTagList)
+ }
+ if (groupTagList.isNotEmpty() && sortedTagList.none { it.name == groupsTagTitleName }) {
+ sortedTagList.add(groupsTagTitle)
+ sortedTagList.addAll(groupTagList)
+ }
+ if (otherTagList.isNotEmpty() && sortedTagList.none { it.name == otherTagTitleName }) {
+ sortedTagList.add(otherTagTitle)
+ sortedTagList.addAll(otherTagList)
+ }
+
+ tags = sortedTagList
+ }
+
+
private fun editButtonClicked() {
- isEditing = !isEditing
- if (isEditing) {
+ isEditingTags = !isEditingTags
+ if (isEditingTags) {
startEditing()
} else {
stopEditing()
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/TaskSchedulingControls.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/TaskSchedulingControls.kt
index 368f95f43b..fca57d36df 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/TaskSchedulingControls.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/tasks/form/TaskSchedulingControls.kt
@@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.views.tasks.form
import android.app.DatePickerDialog
import android.content.Context
import android.content.DialogInterface
+import android.content.res.ColorStateList
import android.icu.text.MessageFormat
import android.os.Build
import android.text.TextUtils
@@ -321,14 +322,14 @@ class TaskSchedulingControls @JvmOverloads constructor(
}
private fun styleButtonAsActive(button: TextView) {
- button.setTextColor(context.getThemeColor(R.attr.colorTintedBackground))
- button.background.mutate().setTint(tintColor)
+ button.setTextColor(context.getThemeColor(R.attr.tintedUiDetails))
+ button.backgroundTintList = ColorStateList.valueOf(context.getThemeColor(R.attr.tintedUiMain))
button.contentDescription = toContentDescription(button.text, true)
}
private fun styleButtonAsInactive(button: TextView) {
- button.setTextColor(context.getThemeColor(R.attr.colorPrimaryDark))
- button.background.mutate().setTint(context.getThemeColor(R.attr.colorTintedBackgroundOffset))
+ button.setTextColor(context.getThemeColor(R.attr.textColorTintedSecondary))
+ button.backgroundTintList = ColorStateList.valueOf(context.getThemeColor(R.attr.colorTintedBackgroundOffset))
button.contentDescription = toContentDescription(button.text, false)
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt
index b8af372888..90e6034314 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/yesterdailies/YesterdailyDialog.kt
@@ -13,7 +13,9 @@ import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
@@ -254,10 +256,10 @@ class YesterdailyDialog private constructor(
val additionalData = HashMap()
additionalData["task count"] = sortedTasks?.size ?: 0
- AmplitudeManager.sendEvent(
+ Analytics.sendEvent(
"show cron",
- AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
- AmplitudeManager.EVENT_HITTYPE_EVENT,
+ EventCategory.BEHAVIOUR,
+ HitType.EVENT,
additionalData
)
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
index 7bbf207a1b..e534b9ec72 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
+++ b/Habitica/src/main/java/com/habitrpg/android/habitica/widget/BaseWidgetProvider.kt
@@ -7,7 +7,9 @@ import android.os.Bundle
import android.widget.RemoteViews
import android.widget.Toast
import com.habitrpg.android.habitica.data.UserRepository
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
+import com.habitrpg.android.habitica.helpers.Analytics
+import com.habitrpg.android.habitica.helpers.EventCategory
+import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
import javax.inject.Inject
@@ -91,13 +93,13 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
super.onEnabled(context)
val additionalData = HashMap()
additionalData["identifier"] = this.javaClass.simpleName
- AmplitudeManager.sendEvent("widgets", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_CREATE_WIDGET, additionalData)
+ Analytics.sendEvent("widgets", EventCategory.BEHAVIOUR, HitType.CREATE_WIDGET, additionalData)
}
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
val additionalData = HashMap()
additionalData["identifier"] = this.javaClass.simpleName
- AmplitudeManager.sendEvent("widgets", AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR, AmplitudeManager.EVENT_HITTYPE_REMOVE_WIDGET, additionalData)
+ Analytics.sendEvent("widgets", EventCategory.BEHAVIOUR, HitType.REMOVE_WIDGET, additionalData)
super.onDeleted(context, appWidgetIds)
}
}
diff --git a/Habitica/src/release/java/com/habitrpg/android/habitica/ReleaseDeveloperModule.kt b/Habitica/src/release/java/com/habitrpg/android/habitica/ReleaseDeveloperModule.kt
index dcbabac7a1..5de847b349 100644
--- a/Habitica/src/release/java/com/habitrpg/android/habitica/ReleaseDeveloperModule.kt
+++ b/Habitica/src/release/java/com/habitrpg/android/habitica/ReleaseDeveloperModule.kt
@@ -2,11 +2,6 @@ package com.habitrpg.android.habitica
import android.content.Context
import com.habitrpg.android.habitica.modules.DeveloperModule
-import com.habitrpg.android.habitica.proxy.AnalyticsManagerImpl
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
class ReleaseDeveloperModule : DeveloperModule() {
- override fun provideAnalyticsManager(context: Context): AnalyticsManager {
- return AnalyticsManagerImpl(context)
- }
}
diff --git a/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt b/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt
deleted file mode 100644
index 6f957f2e35..0000000000
--- a/Habitica/src/release/java/com/habitrpg/android/habitica/proxy/AnalyticsManagerImpl.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.habitrpg.android.habitica.proxy
-
-import android.content.Context
-import android.os.Bundle
-import com.google.firebase.analytics.FirebaseAnalytics
-import com.google.firebase.crashlytics.FirebaseCrashlytics
-import com.habitrpg.android.habitica.helpers.AmplitudeManager
-import com.habitrpg.common.habitica.helpers.AnalyticsManager
-
-class AnalyticsManagerImpl(context: Context) : AnalyticsManager {
-
- private val firebaseAnalytics = FirebaseAnalytics.getInstance(context)
-
- override fun logException(t: Throwable) {
- FirebaseCrashlytics.getInstance().recordException(t)
- }
-
- override fun setUserIdentifier(identifier: String) {
- FirebaseCrashlytics.getInstance().setUserId(identifier)
- AmplitudeManager.amplitude.setUserId(identifier)
- }
-
- override fun setUserProperty(identifier: String, value: String) {
- firebaseAnalytics.setUserProperty(identifier, value)
- }
-
- override fun logError(msg: String) {
- FirebaseCrashlytics.getInstance().log(msg)
- }
-
- override fun logEvent(eventName: String, data: Bundle) {
- firebaseAnalytics.logEvent(eventName, data)
- }
-}
diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt
index 7849e9f5b2..cf3c43035b 100644
--- a/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt
+++ b/Habitica/src/test/java/com/habitrpg/android/habitica/data/implementation/TaskRepositoryImplTest.kt
@@ -8,6 +8,7 @@ import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.models.tasks.TaskList
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
+import com.habitrpg.android.habitica.modules.AuthenticationHandler
import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
import com.habitrpg.shared.habitica.models.tasks.TaskType
import com.habitrpg.shared.habitica.models.tasks.TasksOrder
@@ -37,10 +38,11 @@ class TaskRepositoryImplTest : WordSpec({
every { localRepository.executeTransaction(transaction = capture(slot)) } answers {
slot.captured(mockk(relaxed = true))
}
+ val authenticationHandler = mockk()
repository = TaskRepositoryImpl(
localRepository,
apiClient,
- "",
+ authenticationHandler,
mockk(relaxed = true),
mockk(relaxed = true)
)
diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/helpers/MarkdownProcessingTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/helpers/MarkdownProcessingTest.kt
new file mode 100644
index 0000000000..bf484bed06
--- /dev/null
+++ b/Habitica/src/test/java/com/habitrpg/android/habitica/helpers/MarkdownProcessingTest.kt
@@ -0,0 +1,86 @@
+package com.habitrpg.android.habitica.helpers
+
+import io.kotest.core.spec.style.WordSpec
+import io.kotest.matchers.shouldBe
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+class MarkdownProcessingTest : WordSpec({
+ "processMarkdown" should {
+ "replace image and link markdown correctly" {
+ val input = "[Habitica Wiki](https://habitica.fandom.com/wiki/Habitica_Wiki) and ![img](https://habitica-assets.s3.amazonaws.com/mobileApp/images/gold.png\"Habitica Gold\")"
+ val output = processMarkdown(input)
+ output shouldBe "[Habitica Wiki](https://habitica.fandom.com/wiki/Habitica_Wiki) and ![img](https://habitica-assets.s3.amazonaws.com/mobileApp/images/gold.png \"Habitica Gold\")"
+ }
+ }
+
+ "preprocessImageMarkdown" should {
+ "add space before title in png image" {
+ val input = "![img](https://habitica-assets.s3.amazonaws.com/mobileApp/images/gold.png\"Habitica Gold\")"
+ val output = preprocessImageMarkdown(input)
+ output shouldBe "![img](https://habitica-assets.s3.amazonaws.com/mobileApp/images/gold.png \"Habitica Gold\")"
+ }
+
+ "not modify non-image markdown" {
+ val input = "[Habitica Wiki](https://habitica.fandom.com/wiki/Habitica_Wiki)"
+ val output = preprocessImageMarkdown(input)
+ output shouldBe "[Habitica Wiki](https://habitica.fandom.com/wiki/Habitica_Wiki)"
+ }
+ }
+
+ "preprocessMarkdownLinks" should {
+ "sanitize link url" {
+ val input = "[Habitica Wiki](https://habi tica.fandom.com/wiki/Habitica_Wiki)"
+ val output = preprocessMarkdownLinks(input)
+ output shouldBe "[Habitica Wiki](https://habitica.fandom.com/wiki/Habitica_Wiki)"
+ }
+
+ "not modify non-link markdown" {
+ val input = "![img](https://habitica-assets.s3.amazonaws.com/mobileApp/images/gold.png\"Habitica Gold\")"
+ val output = preprocessMarkdownLinks(input)
+ output shouldBe "![img](https://habitica-assets.s3.amazonaws.com/mobileApp/images/gold.png\"Habitica Gold\")"
+ }
+ }
+
+}) {
+ companion object {
+ fun processMarkdown(input: String): String {
+ var processedInput = preprocessMarkdownLinks(input)
+ processedInput = preprocessImageMarkdown(processedInput)
+ return processedInput
+ }
+
+ fun preprocessImageMarkdown(markdown: String): String {
+ val regex = Regex("""!\[.*?]\(.*?".*?"\)""")
+ return markdown.replace(regex) { matchResult ->
+ val match = matchResult.value
+ if (match.contains(".png\"")) {
+ match.replace(".png\"", ".png \"")
+ } else {
+ match
+ }
+ }
+ }
+
+ fun preprocessMarkdownLinks(input: String): String {
+ val linkPattern = "\\[([^\\]]+)\\]\\(([^\\)]+)\\)"
+ val multilineLinkPattern = Pattern.compile(linkPattern, Pattern.DOTALL)
+ val matcher = multilineLinkPattern.matcher(input)
+
+ val sb = StringBuffer(input.length)
+
+ while (matcher.find()) {
+ val linkText = matcher.group(1)
+ val url = matcher.group(2)
+ val sanitizedUrl = url.replace(Regex("\\s"), "")
+ val correctedLink = "[$linkText]($sanitizedUrl)"
+ matcher.appendReplacement(sb, Matcher.quoteReplacement(correctedLink))
+ }
+ matcher.appendTail(sb)
+
+ return sb.toString()
+ }
+ }
+}
+
+
diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/helpers/NumberAbbreviatorTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/helpers/NumberAbbreviatorTest.kt
deleted file mode 100644
index a932b70efc..0000000000
--- a/Habitica/src/test/java/com/habitrpg/android/habitica/helpers/NumberAbbreviatorTest.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.habitrpg.android.habitica.helpers
-
-import android.content.Context
-import com.habitrpg.android.habitica.R
-import com.habitrpg.common.habitica.helpers.NumberAbbreviator.abbreviate
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.datatest.withData
-import io.kotest.matchers.shouldBe
-import io.mockk.clearMocks
-import io.mockk.every
-import io.mockk.mockk
-import java.util.Locale
-
-class NumberAbbreviatorTest : StringSpec({
- val mockContext = mockk()
- beforeSpec {
- Locale.setDefault(Locale.US)
- every { mockContext.getString(R.string.thousand_abbrev) } returns "k"
- every { mockContext.getString(R.string.million_abbrev) } returns "m"
- every { mockContext.getString(R.string.billion_abbrev) } returns "b"
- every { mockContext.getString(R.string.trillion_abbrev) } returns "t"
- every { mockContext.getString(R.string.quadrillion_abbrev) } returns "q"
- }
-
- withData(
- Triple(215.0, "215", 2),
- Triple(2.05, "2.05", 2),
- Triple(5.406, "5.4", 2),
- Triple(-20.42, "-20.42", 2),
- Triple(2550.0, "2.55k", 2),
- Triple(-1020.42, "-1.02k", 2),
- Triple(9990000.0, "9.99m", 2),
- Triple(1990000000.0, "1.99b", 2),
- Triple(1990000000000.0, "1.99t", 2),
- Triple(-1990000000.42, "-1.99b", 2),
- Triple(1000.0, "1k", 2),
- Triple(1500.0, "1.5k", 2),
- Triple(1500.0, "1k", 0),
- Triple(-1302.42, "-1.3k", 2),
- Triple(9999.0, "9.99k", 2),
- Triple(-20.42, "-20", 0),
- Triple(40.2412, "40", 0),
- Triple(0.5, "0.5", 0),
- Triple(0.328, "0.32", 0),
- Triple(-0.99, "-0.99", 0)
- ) { (input, output, decimals) ->
- abbreviate(mockContext, input, decimals) shouldBe output
- }
-
- afterSpec { clearMocks(mockContext) }
-})
diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModelTest.kt b/Habitica/src/test/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModelTest.kt
index 0dc572e084..f4be490988 100644
--- a/Habitica/src/test/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModelTest.kt
+++ b/Habitica/src/test/java/com/habitrpg/android/habitica/ui/viewmodels/inventory/equipment/EquipmentOverviewViewModelTest.kt
@@ -1,5 +1,7 @@
package com.habitrpg.android.habitica.ui.viewmodels.inventory.equipment
+import com.habitrpg.android.habitica.data.InventoryRepository
+import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.models.user.Preferences
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
@@ -10,9 +12,10 @@ import io.mockk.every
import io.mockk.mockk
class EquipmentOverviewViewModelTest : WordSpec({
- val viewModel = EquipmentOverviewViewModel()
val mainUserViewmodel = mockk()
- viewModel.userViewModel = mainUserViewmodel
+ val userRepository = mockk()
+ val inventoryRepository = mockk()
+ val viewModel = EquipmentOverviewViewModel(userRepository, mainUserViewmodel, inventoryRepository)
"usesAutoEquip" should {
"return true if user has it set" {
every { mainUserViewmodel.user.value } returns User().apply {
diff --git a/Screenshot_20230824_132939.png b/Screenshot_20230824_132939.png
new file mode 100644
index 0000000000..810367be90
Binary files /dev/null and b/Screenshot_20230824_132939.png differ
diff --git a/Screenshot_20230824_133756.png b/Screenshot_20230824_133756.png
new file mode 100644
index 0000000000..2b87f9d986
Binary files /dev/null and b/Screenshot_20230824_133756.png differ
diff --git a/Screenshot_20230825_151442.png b/Screenshot_20230825_151442.png
new file mode 100644
index 0000000000..99cd16807f
Binary files /dev/null and b/Screenshot_20230825_151442.png differ
diff --git a/build.gradle b/build.gradle
index 56a04de59a..bd4791315a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
@@ -10,27 +12,28 @@ buildscript {
accompanist_version = '0.28.0'
amplitude_version = '1.6.1'
appcompat_version = '1.6.1'
- coil_version = '2.2.2'
- compose_version = '1.4.3'
- core_ktx_version = '1.10.0'
- coroutines_version = '1.6.4'
- daggerhilt_version = '2.44.2'
+ coil_version = '2.4.0'
+ compose_version = '1.5.2'
+ core_ktx_version = '1.12.0'
+ coroutines_version = '1.7.2'
+ daggerhilt_version = '2.47'
firebase_bom = '31.3.0'
- kotest_version = '5.5.5'
- kotlin_version = '1.8.20'
- ktlint_version = '0.48.2'
- lifecycle_version = '2.6.1'
+ kotest_version = '5.6.2'
+ kotlin_version = '1.9.10'
+ ktlint_version = '0.50.0'
+ lifecycle_version = '2.6.2'
markwon_version = '4.6.2'
mockk_version = '1.13.4'
- moshi_version = '1.14.0'
- navigation_version = '2.5.3'
- okhttp_version = '4.10.0'
- play_wearables_version = '18.0.0'
- play_auth_version = '20.5.0'
- preferences_version = '1.2.0'
+ moshi_version = '1.15.0'
+ navigation_version = '2.7.3'
+ okhttp_version = '4.11.0'
+ paging_version = '3.2.1'
+ play_wearables_version = '18.1.0'
+ play_auth_version = '20.7.0'
+ preferences_version = '1.2.1'
realm_version = '1.0.2'
retrofit_version = '2.9.0'
- recyclerview_version = '1.3.0'
+ recyclerview_version = '1.3.1'
}
repositories {
@@ -39,10 +42,10 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.0.1'
+ classpath 'com.android.tools.build:gradle:8.1.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
- classpath 'com.google.gms:google-services:4.3.15'
- classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.5'
+ classpath 'com.google.gms:google-services:4.4.0'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
classpath "io.realm:realm-gradle-plugin:10.13.2-transformer-api"
classpath("io.realm.kotlin:gradle-plugin:$realm_version")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@@ -115,7 +118,7 @@ tasks.named("detekt").configure {
}
task allUnitTests(type: GradleBuild) {
- tasks = [':Habitica:testProdDebugUnitTest', ':wearos:testProdDebugUnitTest', ':common:testProdDebugUnitTest', ':shared:testDebugUnitTest']
+ tasks = [':Habitica:testProdDebugUnitTest', ':wearos:testProdDebugUnitTest', ':common:testProdDebugUnitTest']
}
subprojects {
diff --git a/common/build.gradle.kts b/common/build.gradle.kts
index 06f79a85eb..dbda9921a5 100644
--- a/common/build.gradle.kts
+++ b/common/build.gradle.kts
@@ -18,6 +18,12 @@ android {
consumerProguardFiles("consumer-rules.pro")
}
+ testOptions {
+ unitTests {
+ }
+ animationsDisabled = true
+ }
+
buildTypes {
release {
isMinifyEnabled = true
@@ -83,6 +89,8 @@ val markwon_version: String by rootExtra
val coil_version: String by rootExtra
val mockk_version: String by rootExtra
val kotest_version: String by rootExtra
+val kotlin_version: String by rootExtra
+val navigation_version: String by rootExtra
dependencies {
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
@@ -100,14 +108,21 @@ dependencies {
// Image Management Library
implementation("io.coil-kt:coil:$coil_version")
implementation("io.coil-kt:coil-gif:$coil_version")
+ implementation("androidx.recyclerview:recyclerview:1.3.1")
+ implementation("androidx.navigation:navigation-common-ktx:$navigation_version")
+ implementation("androidx.navigation:navigation-runtime-ktx:$navigation_version")
+ implementation("com.google.android.material:material:1.9.0")
testImplementation("io.mockk:mockk:$mockk_version")
testImplementation("io.mockk:mockk-android:$mockk_version")
testImplementation("io.kotest:kotest-runner-junit5:$kotest_version")
testImplementation("io.kotest:kotest-assertions-core:$kotest_version")
testImplementation("io.kotest:kotest-framework-datatest:$kotest_version")
+ testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
+
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
implementation(project(":shared"))
}
@@ -118,6 +133,12 @@ android.testOptions {
}
}
+tasks.withType {
+ this.testLogging {
+ this.showStandardStreams = true
+ }
+}
+
// Add Habitica Properties to buildConfigField
val HRPG_PROPS_FILE = File(projectDir.absolutePath + "/../habitica.properties")
if (HRPG_PROPS_FILE.canRead()) {
diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/BaseViewModelExtensions.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/BaseViewModelExtensions.kt
index c0188b1401..fa5d269225 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/extensions/BaseViewModelExtensions.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/BaseViewModelExtensions.kt
@@ -8,8 +8,8 @@ fun LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observ
observe(
lifecycleOwner,
object : Observer {
- override fun onChanged(t: T?) {
- observer.onChanged(t)
+ override fun onChanged(value: T) {
+ observer.onChanged(value)
removeObserver(this)
}
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt
index 956e835740..4b6c83ee30 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/DataBindingUtils.kt
@@ -91,8 +91,8 @@ object DataBindingUtils {
imageName.startsWith("handleless") -> "chair_$imageName"
else -> imageName
}
- return name + if (imageFormat == null && FILEFORMAT_MAP.containsKey(name)) {
- "." + FILEFORMAT_MAP[name]
+ return name + if (imageFormat == null && FILEFORMAT_MAP.containsKey(imageName)) {
+ "." + FILEFORMAT_MAP[imageName]
} else {
".${imageFormat ?: "png"}"
}
@@ -161,6 +161,7 @@ object DataBindingUtils {
tempMap["weapon_special_0"] = "gif"
tempMap["shield_special_0"] = "gif"
tempMap["Pet-Wolf-Cerberus"] = "gif"
+ tempMap["stable_Pet-Wolf-Cerberus"] = "gif"
tempMap["armor_special_ks2019"] = "gif"
tempMap["slim_armor_special_ks2019"] = "gif"
tempMap["broad_armor_special_ks2019"] = "gif"
@@ -169,6 +170,7 @@ object DataBindingUtils {
tempMap["shield_special_ks2019"] = "gif"
tempMap["weapon_special_ks2019"] = "gif"
tempMap["Pet-Gryphon-Gryphatrice"] = "gif"
+ tempMap["stable_Pet-Gryphon-Gryphatrice"] = "gif"
tempMap["Mount_Head_Gryphon-Gryphatrice"] = "gif"
tempMap["Mount_Body_Gryphon-Gryphatrice"] = "gif"
tempMap["background_clocktower"] = "gif"
@@ -196,7 +198,7 @@ object DataBindingUtils {
tempNameMap["shield_special_0"] = "BackerOnly-Shield-TormentedSkull"
tempNameMap["weapon_special_0"] = "BackerOnly-Weapon-DarkSoulsBlade"
tempNameMap["weapon_special_critical"] = "weapon_special_critical"
- tempNameMap["Pet-Wolf-Cerberus"] = "Pet-Wolf-Cerberus"
+ tempNameMap["Pet-Wolf-Cerberus"] = "BackerOnly-Pet-CerberusPup"
FILENAME_MAP = tempNameMap
}
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/TextViewExtensions.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/TextViewExtensions.kt
index acc124aea3..d29890514a 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/extensions/TextViewExtensions.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/TextViewExtensions.kt
@@ -2,12 +2,24 @@ package com.habitrpg.common.habitica.extensions
import android.text.SpannableStringBuilder
import android.text.Spanned
+import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.URLSpan
import android.view.View
import android.widget.TextView
+class HabiticaClickableSpan(val onClickAction: () -> Unit): ClickableSpan() {
+ override fun onClick(widget: View) {
+ onClickAction()
+ }
+
+ override fun updateDrawState(ds: TextPaint) {
+ super.updateDrawState(ds)
+ ds.isUnderlineText = false
+ }
+}
+
fun TextView.handleUrlClicks(onClicked: ((String) -> Unit)? = null) {
// create span builder and replaces current text with it
text = SpannableStringBuilder.valueOf(text).apply {
@@ -15,10 +27,8 @@ fun TextView.handleUrlClicks(onClicked: ((String) -> Unit)? = null) {
getSpans(0, length, URLSpan::class.java).forEach {
// add new clickable span at the same position
setSpan(
- object : ClickableSpan() {
- override fun onClick(widget: View) {
- onClicked?.invoke(it.url)
- }
+ HabiticaClickableSpan {
+ onClicked?.invoke(it.url)
},
getSpanStart(it),
getSpanEnd(it),
diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt
index be01acf012..10689bf1d1 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt
@@ -2,9 +2,12 @@ package com.habitrpg.android.habitica.extensions
import android.animation.ObjectAnimator
import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewTreeObserver
import com.habitrpg.common.habitica.extensions.dpToPx
+import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
fun View.setScaledPadding(context: Context?, left: Int, top: Int, right: Int, bottom: Int) {
this.setPadding(left.dpToPx(context), top.dpToPx(context), right.dpToPx(context), bottom.dpToPx(context))
@@ -38,3 +41,17 @@ fun View.fadeInAnimation(duration: Long = 500) {
fadeInAnimation.duration = duration
fadeInAnimation.start()
}
+
+fun View.flash() {
+ val originalColor = (background as? ColorDrawable)?.color
+ if (this.context.isUsingNightModeResources()) {
+ setBackgroundColor(Color.DKGRAY)
+ } else {
+ setBackgroundColor(Color.LTGRAY)
+ }
+
+ postDelayed({
+ originalColor?.let { setBackgroundColor(it) } ?: setBackgroundResource(0)
+ }, 100)
+}
+
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/AnalyticsManager.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/AnalyticsManager.kt
deleted file mode 100644
index f303bb1ae8..0000000000
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/AnalyticsManager.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.habitrpg.common.habitica.helpers
-
-import android.os.Bundle
-
-interface AnalyticsManager {
- fun logException(t: Throwable)
- fun setUserIdentifier(identifier: String)
- fun setUserProperty(identifier: String, value: String)
- fun logError(msg: String)
- fun logEvent(eventName: String, data: Bundle)
-}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/Animations.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/Animations.kt
index b746c08a76..478c023691 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/Animations.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/Animations.kt
@@ -61,11 +61,13 @@ object Animations {
anim.start()
}
- fun circularHide(view: View) {
+ fun circularHide(view: View, duration: Long = 300) {
val cx = view.width / 2
val cy = view.height / 2
val initialRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat()
val anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, initialRadius, 0f)
+ anim.duration = duration
+ anim.interpolator = AccelerateInterpolator()
anim.doOnEnd {
view.visibility = View.INVISIBLE
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/ExceptionHandler.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/ExceptionHandler.kt
index 6782352b72..586b6e0d32 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/ExceptionHandler.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/ExceptionHandler.kt
@@ -10,14 +10,14 @@ import kotlinx.coroutines.launch
import java.io.IOException
class ExceptionHandler {
- private var analyticsManager: AnalyticsManager? = null
+ private var exceptionLogger: ((Throwable) -> Unit)? = null
companion object {
private var instance = ExceptionHandler()
- fun init(analyticsManager: AnalyticsManager? = null) {
- instance.analyticsManager = analyticsManager
+ fun init(exceptionLogger: ((Throwable) -> Unit)? = null) {
+ instance.exceptionLogger = exceptionLogger
}
fun coroutine(handler: ((Throwable) -> Unit)? = null): CoroutineExceptionHandler {
@@ -38,7 +38,7 @@ class ExceptionHandler {
throwable !is HttpException &&
throwable !is CancellationException
) {
- instance.analyticsManager?.logException(throwable)
+ instance.exceptionLogger?.invoke(throwable)
}
}
}
diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/MainNavigationController.kt
similarity index 85%
rename from Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt
rename to common/src/main/java/com/habitrpg/common/habitica/helpers/MainNavigationController.kt
index 8f5429c5a7..a9dfc1e853 100644
--- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/MainNavigationController.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/MainNavigationController.kt
@@ -1,5 +1,6 @@
-package com.habitrpg.android.habitica.helpers
+package com.habitrpg.common.habitica.helpers
+import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -23,7 +24,7 @@ object MainNavigationController {
get() = controllerReference?.get() != null
fun setup(navController: NavController) {
- this.controllerReference = WeakReference(navController)
+ controllerReference = WeakReference(navController)
}
fun updateLabel(destinationID: Int, label: String) {
@@ -67,6 +68,13 @@ object MainNavigationController {
fun navigate(uri: Uri) {
if (navController?.graph?.hasDeepLink(uri) == true) {
navController?.navigate(uri)
+ } else {
+ val intent = Intent(Intent.ACTION_VIEW, uri)
+ try {
+ navController?.context?.startActivity(intent)
+ } catch (e: ActivityNotFoundException) {
+ // No application can handle the link
+ }
}
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt
index 44e7e5e70d..727bbfdb5c 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt
@@ -1,8 +1,6 @@
package com.habitrpg.common.habitica.helpers
-import android.content.ActivityNotFoundException
import android.content.Context
-import android.content.Intent
import android.graphics.Rect
import android.net.Uri
import android.text.SpannableString
@@ -45,7 +43,7 @@ object MarkdownParser {
.addSchemeHandler(FileSchemeHandler.createWithAssets(context.assets))
}
)
- .usePlugin(this.createImageSizeResolverScaleDpiPlugin(context))
+ .usePlugin(createImageSizeResolverScaleDpiPlugin(context))
.usePlugin(MovementMethodPlugin.create(LinkMovementMethod.getInstance()))
.usePlugin(LinkifyPlugin.create(Linkify.WEB_URLS))
.build()
@@ -228,10 +226,5 @@ private fun handleUrlClicks(context: Context, url: String) {
Uri.parse(url)
}
}
- val intent = Intent(Intent.ACTION_VIEW, webpage)
- try {
- context.startActivity(intent)
- } catch (e: ActivityNotFoundException) {
- // No application can handle the link
- }
+ MainNavigationController.navigate(webpage)
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
index eb74731780..74fd1e0851 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
@@ -32,10 +32,10 @@ object NumberAbbreviator {
if (number < 0) {
result = "-$result"
}
- return result + abbreviationForCounter(context, counter)
+ return result + abbreviationForCounter(counter)
}
- private fun abbreviationForCounter(context: Context?, counter: Int): String = when (counter) {
+ private fun abbreviationForCounter(counter: Int): String = when (counter) {
0 -> ""
1 -> "k"
2 -> "m"
diff --git a/common/src/main/java/com/habitrpg/common/habitica/helpers/RecyclerViewEmptySupport.kt b/common/src/main/java/com/habitrpg/common/habitica/helpers/RecyclerViewEmptySupport.kt
index f37c48f923..16d3c152bf 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/helpers/RecyclerViewEmptySupport.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/helpers/RecyclerViewEmptySupport.kt
@@ -15,7 +15,7 @@ data class EmptyItem(
var title: String,
var text: String? = null,
var iconResource: Int? = null,
- var buttonLabel: String? = null,
+ var tintedIcon: Boolean = true,
var onButtonTap: (() -> Unit)? = null
)
@@ -46,24 +46,20 @@ class EmptyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = EmptyItemBinding.bind(itemView)
fun bind(emptyItem: EmptyItem?) {
- binding.emptyIconView.setColorFilter(
- ContextCompat.getColor(
- itemView.context,
- R.color.text_dimmed
- ),
- android.graphics.PorterDuff.Mode.MULTIPLY
- )
+ if (emptyItem?.tintedIcon == true) {
+ binding.emptyIconView.setColorFilter(
+ ContextCompat.getColor(
+ itemView.context,
+ R.color.text_dimmed
+ ),
+ android.graphics.PorterDuff.Mode.MULTIPLY
+ )
+ }
emptyItem?.iconResource?.let { binding.emptyIconView.setImageResource(it) }
binding.emptyViewTitle.text = emptyItem?.title
- binding.emptyViewDescription.text = emptyItem?.text
-
- val buttonLabel = emptyItem?.buttonLabel
- if (buttonLabel != null) {
- binding.button.visibility = View.VISIBLE
- binding.button.text = buttonLabel
- binding.button.setOnClickListener { emptyItem.onButtonTap?.invoke() }
- } else {
- binding.button.visibility = View.GONE
+ binding.emptyViewDescription.setMarkdown(emptyItem?.text)
+ if (emptyItem?.onButtonTap != null) {
+ binding.emptyView.setOnClickListener { emptyItem.onButtonTap?.invoke() }
}
}
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt
index 74cdcdcfdf..e81c4d0c62 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/views/AvatarView.kt
@@ -8,12 +8,12 @@ import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Animatable
+import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.ImageView
-import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.marginStart
import androidx.core.view.marginTop
import coil.dispose
@@ -72,7 +72,9 @@ class AvatarView : FrameLayout {
if (BuildConfig.DEBUG && (avatar == null || avatarRectF == null)) {
error("Assertion failed")
}
- val canvasRect = Rect(0, 0, 140.dpToPx(context), 147.dpToPx(context))
+ val viewWidth = if (width > 0) width else (layoutParams?.width ?: 140)
+ val viewHeight = if (height > 0) height else (layoutParams?.height ?: 147)
+ val canvasRect = Rect(0, 0, if (viewWidth > 0) viewWidth else 140.dpToPx(context), if (viewHeight > 0) viewHeight else 147.dpToPx(context))
if (canvasRect.isEmpty) return null
avatarBitmap = Bitmap.createBitmap(
canvasRect.width(),
@@ -82,11 +84,11 @@ class AvatarView : FrameLayout {
avatarBitmap?.let { avatarCanvas = Canvas(it) }
imageViewHolder.forEach {
val lp = it.layoutParams
- val bitmap = it.drawable?.toBitmap(lp.width, lp.height) ?: return@forEach
+ val bitmap = (it.drawable as? BitmapDrawable)?.bitmap ?: return@forEach
avatarCanvas?.drawBitmap(
bitmap,
Rect(0, 0, bitmap.width, bitmap.height),
- Rect(it.marginStart, it.marginTop, bitmap.width, bitmap.height),
+ Rect(it.marginStart, it.marginTop, bitmap.width + it.marginStart, bitmap.height + it.marginTop),
null
)
}
@@ -470,8 +472,10 @@ class AvatarView : FrameLayout {
// full hero box when showMount and showPet is enabled (140w * 147h)
// compact hero box when only showBackground is enabled (114w * 114h)
// hero only box when all show settings disabled (90w * 90h)
- val width = if (this.width > 0) this.width.toFloat() else 140.dpToPx(context).toFloat()
- val height = if (this.height > 0) this.height.toFloat() else 147.dpToPx(context).toFloat()
+ val viewWidth = if (width > 0) width else (layoutParams?.width ?: 140)
+ val viewHeight = if (height > 0) height else (layoutParams?.height ?: 147)
+ val width = if (viewWidth > 0) viewWidth.toFloat() else 140.dpToPx(context).toFloat()
+ val height = if (viewHeight > 0) viewHeight.toFloat() else 147.dpToPx(context).toFloat()
avatarRectF = RectF(0f, 0f, width, height)
avatarMatrix.setRectToRect(RectF(srcRect), avatarRectF, Matrix.ScaleToFit.START)
avatarRectF = RectF(srcRect)
@@ -479,7 +483,7 @@ class AvatarView : FrameLayout {
}
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
initAvatarRectMatrix()
@@ -492,7 +496,7 @@ class AvatarView : FrameLayout {
override fun invalidateDrawable(drawable: Drawable) {
invalidate()
- if (avatarCanvas != null) draw(avatarCanvas)
+ avatarCanvas?.let { draw(it) }
}
enum class LayerType {
diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/PixelArtView.kt b/common/src/main/java/com/habitrpg/common/habitica/views/PixelArtView.kt
index 23a08d2c77..5d65c7c2b7 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/views/PixelArtView.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/views/PixelArtView.kt
@@ -39,34 +39,33 @@ class PixelArtView @JvmOverloads constructor(
var targetWidth = bitmap?.width ?: 0
var targetHeight = bitmap?.height ?: 0
val smallestSide = min(width, height)
+ val divisor = if (targetWidth % 3 == 0 && targetHeight % 3 == 0) 3 else 2
val factor = min(
- (
- if (smallestSide > 0 && targetWidth > 0 && smallestSide != targetWidth) {
- smallestSide / (targetWidth / 3)
- } else {
- 1
- }
- ),
+ if (smallestSide > 0 && targetWidth > 0 && smallestSide != targetWidth) {
+ smallestSide / (targetWidth / divisor)
+ } else {
+ 1
+ },
if (smallestSide > 0 && targetHeight > 0 && smallestSide != targetHeight) {
- smallestSide / (targetHeight / 3)
+ smallestSide / (targetHeight / divisor)
} else {
1
}
)
- targetWidth = (targetWidth / 3) * factor
- targetHeight = (targetHeight / 3) * factor
+ targetWidth = (targetWidth / divisor) * factor
+ targetHeight = (targetHeight / divisor) * factor
val left = (width - targetWidth) / 2
val top = (height - targetHeight) / 2
targetRect = Rect(left, top, left + targetWidth, top + targetHeight)
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
if (bitmap == null) {
super.onDraw(canvas)
return
}
val bitmap = bitmap ?: return
- canvas?.drawBitmap(bitmap, null, targetRect, paint)
+ canvas.drawBitmap(bitmap, null, targetRect, paint)
}
}
diff --git a/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt b/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt
index 7c77dcc1e6..fda887a2f1 100644
--- a/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt
+++ b/common/src/main/java/com/habitrpg/common/habitica/views/ValueBar.kt
@@ -39,6 +39,8 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
binding.progressBar.maxValue = value
}
+ var valueSuffix: String? = null
+
var barForegroundColor: Int
get() = binding.progressBar.barForegroundColor
set(value) {
@@ -82,7 +84,7 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
private fun updateBar() {
binding.progressBar.set(currentValue, maxValue)
binding.progressBar.pendingValue = pendingValue
- setValueText(formatter.format(currentValue) + " / " + formatter.format(maxValue.toInt()))
+ setValueText(formatter.format(currentValue) + " / " + formatter.format(maxValue.toInt()) + " " + (valueSuffix ?: ""))
}
init {
@@ -148,12 +150,10 @@ class ValueBar(context: Context, attrs: AttributeSet?) : FrameLayout(context, at
fun setDescriptionIcon(iconRes: Drawable) {
binding.descriptionIconView.setImageDrawable(iconRes)
- binding.descriptionIconView.visibility = View.VISIBLE
}
fun setDescriptionIcon(bitmap: Bitmap) {
binding.descriptionIconView.setImageBitmap(bitmap)
- binding.descriptionIconView.visibility = View.VISIBLE
}
fun setValueText(valueText: String) {
diff --git a/common/src/main/res/layout/empty_item.xml b/common/src/main/res/layout/empty_item.xml
index b3d38777a0..259fdb6fea 100644
--- a/common/src/main/res/layout/empty_item.xml
+++ b/common/src/main/res/layout/empty_item.xml
@@ -5,39 +5,34 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:gravity="center"
- android:padding="30dp">
+ android:gravity="top|center"
+ android:paddingTop="56dp"
+ android:paddingHorizontal="24dp">
+ android:layout_marginBottom="16dp" />
-
-
-
\ No newline at end of file
+
diff --git a/common/src/main/res/layout/value_bar.xml b/common/src/main/res/layout/value_bar.xml
index 10223b0f8c..0f757cd26d 100644
--- a/common/src/main/res/layout/value_bar.xml
+++ b/common/src/main/res/layout/value_bar.xml
@@ -6,59 +6,61 @@
android:orientation="horizontal">
+ android:id="@+id/icon_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignTop="@+id/progressBar"
+ android:layout_marginEnd="@dimen/bar_icon_padding"
+ android:contentDescription="@null"
+ android:scaleType="center"
+ android:visibility="gone" />
+ android:id="@+id/progressBar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/bar_size"
+ android:layout_toEndOf="@id/icon_view"
+ android:orientation="horizontal" />
+ android:id="@+id/labelWrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/progressBar"
+ android:layout_toEndOf="@id/icon_view"
+ android:paddingTop="2dp">
+ android:id="@+id/secondaryIconView"
+ android:layout_width="@dimen/icon_size"
+ android:layout_height="@dimen/icon_size"
+ android:layout_marginEnd="@dimen/spacing_small"
+ android:visibility="gone" />
+ android:id="@+id/value_text_view"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:fontFamily="sans-serif-medium"
+ android:textSize="14sp"
+ tools:text="50/50" />
+ android:id="@+id/description_icon_view"
+ android:layout_width="@dimen/icon_size"
+ android:layout_height="@dimen/icon_size"
+ android:layout_marginEnd="@dimen/spacing_small"
+ android:visibility="gone" />
+ android:id="@+id/description_text_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fontFamily="sans-serif-medium"
+ android:gravity="center|end"
+ android:textSize="14sp"
+ tools:text="Health" />
diff --git a/common/src/main/res/values-night/colors.xml b/common/src/main/res/values-night/colors.xml
index 81e86ef216..77cc57042e 100644
--- a/common/src/main/res/values-night/colors.xml
+++ b/common/src/main/res/values-night/colors.xml
@@ -11,13 +11,15 @@
@color/gray_10@color/brand_600@color/brand_500
- @color/maroon_100
- @color/red_100
- @color/orange_100
- @color/yellow_100
- @color/green_100
- @color/blue_100
- @color/teal_100
+ @color/maroon_500
+ @color/red_500
+ @color/orange_500
+ @color/yellow_500
+ @color/green_500
+ @color/blue_500
+ @color/teal_500
+ @color/gray_5
+ @color/gray_700#40502B94@color/gray_100
diff --git a/common/src/main/res/values/colors.xml b/common/src/main/res/values/colors.xml
index 0d2d9d881d..3c0fa5f093 100644
--- a/common/src/main/res/values/colors.xml
+++ b/common/src/main/res/values/colors.xml
@@ -158,6 +158,8 @@
@color/green_10@color/blue_10@color/teal_10
+ @color/gray_700
+ @color/gray_5@color/gray_500#40BDA8FF
diff --git a/common/src/test/java/com/habitrpg/common/habitica/helpers/NumberAbbreviatorTest.kt b/common/src/test/java/com/habitrpg/common/habitica/helpers/NumberAbbreviatorTest.kt
new file mode 100644
index 0000000000..687b84dae3
--- /dev/null
+++ b/common/src/test/java/com/habitrpg/common/habitica/helpers/NumberAbbreviatorTest.kt
@@ -0,0 +1,76 @@
+package com.habitrpg.common.habitica.helpers
+
+import android.content.Context
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.datatest.withData
+import io.kotest.matchers.longs.shouldBeLessThan
+import io.kotest.matchers.shouldBe
+import io.mockk.clearMocks
+import io.mockk.every
+import io.mockk.mockk
+import java.util.Locale
+
+class NumberAbbreviatorTest : StringSpec({
+ val mockContext = mockk()
+ beforeSpec {
+ Locale.setDefault(Locale.US)
+ every { mockContext.getString(com.habitrpg.common.habitica.R.string.thousand_abbrev) } returns "k"
+ every { mockContext.getString(com.habitrpg.common.habitica.R.string.million_abbrev) } returns "m"
+ every { mockContext.getString(com.habitrpg.common.habitica.R.string.billion_abbrev) } returns "b"
+ every { mockContext.getString(com.habitrpg.common.habitica.R.string.trillion_abbrev) } returns "t"
+ every { mockContext.getString(com.habitrpg.common.habitica.R.string.quadrillion_abbrev) } returns "q"
+ }
+
+ withData(
+ Triple(215.0, "215", 2),
+ Triple(2.05, "2.05", 2),
+ Triple(5.406, "5.4", 2),
+ Triple(-20.42, "-20.42", 2),
+ Triple(2550.0, "2.55k", 2),
+ Triple(-1020.42, "-1.02k", 2),
+ Triple(9990000.0, "9.99m", 2),
+ Triple(1990000000.0, "1.99b", 2),
+ Triple(1990000000000.0, "1.99t", 2),
+ Triple(-1990000000.42, "-1.99b", 2),
+ Triple(1000.0, "1k", 2),
+ Triple(1500.0, "1.5k", 2),
+ Triple(1500.0, "1k", 0),
+ Triple(-1302.42, "-1.3k", 2),
+ Triple(9999.0, "9.99k", 2),
+ Triple(-20.42, "-20", 0),
+ Triple(40.2412, "40", 0),
+ Triple(0.5, "0.5", 0),
+ Triple(0.328, "0.32", 0),
+ Triple(-0.99, "-0.99", 0)
+ ) { (input, output, decimals) ->
+ NumberAbbreviator.abbreviate(mockContext, input, decimals) shouldBe output
+ }
+
+ "completes quickly" {
+ val iterations = 10000
+ val startTime = System.nanoTime()
+ repeat(iterations) {
+ NumberAbbreviator.abbreviate(mockContext, 201.5, 2) shouldBe "201.5"
+ }
+ val endTime = System.nanoTime()
+
+ val averageDuration = (endTime - startTime) / iterations
+ print("Average duration: $averageDuration")
+ averageDuration shouldBeLessThan 1500
+ }
+
+ "completes large numbers quickly" {
+ val iterations = 10000
+ val startTime = System.nanoTime()
+ repeat(iterations) {
+ NumberAbbreviator.abbreviate(mockContext, 1.9943212354213233E30, 2) shouldBe "1.99"
+ }
+ val endTime = System.nanoTime()
+
+ val averageDuration = (endTime - startTime) / iterations
+ print("Average duration: $averageDuration")
+ averageDuration shouldBeLessThan 2000
+ }
+
+ afterSpec { clearMocks(mockContext) }
+})
diff --git a/fastlane/changelog.txt b/fastlane/changelog.txt
index 7c3602251b..875e671fa0 100644
--- a/fastlane/changelog.txt
+++ b/fastlane/changelog.txt
@@ -1,9 +1,9 @@
-New in 4.2.1:
-It's easier than ever to party up with our newest feature: Look for Party and Find Members!
-
-- Solo players can let Party leaders know they want an invite by going to Menu > Party and tapping 'Look for Party'
-- Party leaders can see players looking for Party by tapping 'Find Members' on the Party screen and send invites from the list
-- Armoire shows in Market when you have all gear
-- Change class from Market when viewing gear from another class
-- Various bug fixes and improvements
-
+New in 4.3:
+It’s easier than ever to party up with our newest feature: Look for Party and Find Members!
+- Solo players can let Party leaders know they want an invite by going to Menu > Party and tapping ‘Look for Party’
+- Party leaders can see players looking for Party by tapping ‘Find Members’ on the Party screen and send invites
+- Can gift Gems by username
+- Bailey notification will show title
+- Selecting a class is more consistent
+- Chat notifications open Party
+- Other various fixes
diff --git a/fastlane/metadata/android/bg/full_description.txt b/fastlane/metadata/android/bg/full_description.txt
new file mode 100644
index 0000000000..6a79dbcf89
--- /dev/null
+++ b/fastlane/metadata/android/bg/full_description.txt
@@ -0,0 +1,28 @@
+Превърнете живота си в игра за повече мотивация и по-добра организация! С Хабитика се забавлявате, докато постигате целите си. Въведете навиците си, ежедневните си цели и задачите, които имате да свършите, и си създайте герой.
+
+Отмятайте задачите си, за да качвате нивото на героя си и да отключвате различни функционалности като екипировка, любимци, умения и дори мисии! Бийте се с чудовища заедно с приятели, за да се подкрепяте взаимно и да се държите отговорни, и използвайте златото си за награди в играта, например екипировка; или създайте свои собствени награди, като например разрешение да гледате епизод от любимия си сериал. Хабитика е приспособим и забавен начин да мотивирате себе си, за да постигнете всичко, което искате.
+
+Функции:
+ • Автоматично повтаряне на задачите, планирани за вашите ежедневни, седмични или месечни процедури
+ • Гъвкав инструмент за проследяване на навици за задачи, които искате да правите няколко пъти на ден или само веднъж от време на време
+ • Традиционен списък със задачи, които трябва да се направят само веднъж
+ • Цветно кодирани задачи и броячи на серии ви помагат да видите как се справяте с един поглед
+ • Система за изравняване, за да визуализирате цялостния си напредък
+ • Тонове колекционерска екипировка и домашни любимци, които да отговарят на вашия личен стил
+ • Включващи персонализации на аватар: инвалидни колички, прически, тонове на кожата и др
+ • Редовни издания на съдържание и сезонни събития, за да поддържате нещата свежи
+ • Партиите ви позволяват да се съюзите с приятели за допълнителна отговорност и да се биете с яростни врагове, като изпълнявате задачи
+ • Предизвикателствата предлагат споделени списъци със задачи, които можете да добавите към личните си задачи
+ • Напомняния и уиджети, които да ви помогнат да сте на път
+ • Персонализируеми цветови теми с тъмен и светъл режим
+ • Синхронизиране между устройства
+
+Искате още повече гъвкавост, за да поемате задачите си в движение? Имаме приложение Wear OS на часовника!
+
+Функции на Wear OS:
+ • Преглеждайте, създавайте и изпълнявайте Habits, Dailies и To do’s
+ • Получавайте награди за вашите усилия с опит, храна, яйца и отвари
+ • Проследявайте статистиката си с динамични ленти за напредък
+ • Покажете своя зашеметяващ пикселен аватар върху циферблата на часовника
+
+Ако имате въпроси, пишете на mobile@habitica.com! А ако приложението Ви харесва, ще се радваме да оставите коментар или рецензия.
\ No newline at end of file
diff --git a/fastlane/metadata/android/bg/short_description.txt b/fastlane/metadata/android/bg/short_description.txt
new file mode 100644
index 0000000000..3f4686a350
--- /dev/null
+++ b/fastlane/metadata/android/bg/short_description.txt
@@ -0,0 +1 @@
+Превърнете живота си в игра за повече мотивация и по-добра организация!
\ No newline at end of file
diff --git a/fastlane/metadata/android/bg/title.txt b/fastlane/metadata/android/bg/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/bg/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/bg/video.txt b/fastlane/metadata/android/bg/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/de-DE/full_description.txt b/fastlane/metadata/android/de-DE/full_description.txt
index a3697eb19e..5eaf98f7d7 100644
--- a/fastlane/metadata/android/de-DE/full_description.txt
+++ b/fastlane/metadata/android/de-DE/full_description.txt
@@ -2,4 +2,27 @@ Betrachte Dein Leben als ein Spiel, um motiviert und organisiert zu bleiben! Hab
Hake Deine Aufgaben ab, damit Dein Avatar auf ein höheres Level aufsteigen kann und um weitere Funktionen freizuschalten, wie beispielsweise Ausrüstung, Haustiere, Fähigkeiten und auch Quests! Du kannst Monster mit Deinen Freunden bekämpfen und euch so gegenseitig unterstützen. Du kannst Dein Gold dazu nutzen um spielbezogene Belohnungen zu kaufen, oder für individuelle Belohnungen, wie beispielsweise eine Episode deiner lieblings Fehrnsehserie zu gucken. Habitica ist flexibel, kommunikativ und macht Spaß, es bietet eine tolle Möglichkeit Dich zu jedem Ziel zu motivieren.
+Merkmale:
+ • Automatisch wiederkehrende Aufgaben, die für Ihre täglichen, wöchentlichen oder monatlichen Routinen geplant sind
+ • Flexibler Gewohnheitstracker für Aufgaben, die Sie mehrmals am Tag oder nur ab und zu erledigen möchten
+ • Traditionelle Aufgabenliste für Aufgaben, die nur einmal erledigt werden müssen
+ • Farbcodierte Aufgaben und Streak-Zähler helfen Ihnen, auf einen Blick zu sehen, wie es Ihnen geht
+ • Nivelliersystem zur Visualisierung Ihres Gesamtfortschritts
+ • Tonnenweise sammelbare Ausrüstung und Haustiere, passend zu Ihrem persönlichen Stil
+ • Inklusive Avatar-Anpassungen: Rollstühle, Frisuren, Hauttöne und mehr
+ • Regelmäßige Content-Veröffentlichungen und saisonale Events, um die Dinge auf dem neuesten Stand zu halten
+ • In Gruppen können Sie sich mit Freunden zusammenschließen, um zusätzliche Verantwortung zu übernehmen und durch das Erledigen von Aufgaben gegen erbitterte Gegner anzutreten
+ • Herausforderungen bieten gemeinsame Aufgabenlisten, die Sie zu Ihren persönlichen Aufgaben hinzufügen können
+ • Erinnerungen und Widgets, die Ihnen helfen, den Überblick zu behalten
+ • Anpassbare Farbthemen mit Dunkel- und Hellmodus
+ • Synchronisierung zwischen Geräten
+
+Möchten Sie noch mehr Flexibilität, um Ihre Aufgaben auch unterwegs zu erledigen? Wir haben eine Wear OS-App auf der Uhr!
+
+Wear OS-Funktionen:
+ • Gewohnheiten, tägliche Aufgaben und Aufgaben ansehen, erstellen und abschließen
+ • Erhalten Sie Belohnungen für Ihre Bemühungen mit Erfahrung, Essen, Eiern und Tränken
+ • Verfolgen Sie Ihre Statistiken mit dynamischen Fortschrittsbalken
+ • Zeigen Sie Ihren atemberaubenden Pixel-Avatar auf dem Zifferblatt
+
Falls Du irgendwelche Fragen hast, kannst Du uns gerne Feedback an mobile@habitica.com schicken! Und falls Dir unsere App gefällt, würden wir uns freuen, wenn Du uns bewertest.
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-GB/full_description.txt b/fastlane/metadata/android/en-GB/full_description.txt
new file mode 100644
index 0000000000..d92bf555a8
--- /dev/null
+++ b/fastlane/metadata/android/en-GB/full_description.txt
@@ -0,0 +1,39 @@
+Habitica is a free habit-building and productivity app that uses retro RPG elements to gamify your tasks and goals.
+
+Use Habitica to help with ADHD, self care, New Year’s resolutions, household chores, work tasks, creative projects, fitness goals, back-to-school routines, and more!
+
+How it works:
+Create an avatar then add tasks, chores, or goals you’d like to work on. When you do something in real life, check it off in the app and receive gold, experience, and items that can be used in-game!
+
+Features:
+ • Automatically repeating tasks scheduled for your daily, weekly, or monthly routines
+ • Flexible habit tracker for tasks you want to do multiple times a day or only once in awhile
+ • Traditional to do list for tasks that only need to be done once
+ • Colour coded tasks and streak counters help you see how you’re doing at a glance
+ • Leveling system to visualize your overall progress
+ • Tons of collectable gear and pets to suit your personal style
+ • Inclusive avatar customizations: wheelchairs, hair styles, skin tones, and more
+ • Regular content releases and seasonal events to keep things fresh
+ • Parties let you team up with friends for extra accountability and battle fierce foes by completing tasks
+ • Challenges offer shared task lists you can add to your personal tasks
+ • Reminders and widgets to help keep you on track
+ • Customizable colour themes with dark and light mode
+ • Syncing across devices
+
+Want even more flexibility to take your tasks on the go? We have a Wear OS app on the watch!
+
+Wear OS features:
+ • View, create, and complete Habits, Dailies, and To do’s
+ • Receive rewards for your efforts with experience, food, eggs, and potions
+ • Track your stats with dynamic progress bars
+ • Show off your stunning pixel avatar on the watch face
+
+—
+
+Run by a small team, Habitica is an open-source app made better by contributors who create translations, bug fixes, and more. If you’d like to contribute, you can check out our GitHub or reach out for more information!
+
+We highly value community, privacy, and transparency. Rest assured, your tasks remain private and we never sell your personal data to third parties.
+
+Questions or feedback? Feel free to reach us at admin@habitica.com! If you’re enjoying Habitica, we’d be thrilled if you leave us a review.
+
+Start your journey towards productivity, download Habitica now!
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-GB/short_description.txt b/fastlane/metadata/android/en-GB/short_description.txt
new file mode 100644
index 0000000000..809bf57670
--- /dev/null
+++ b/fastlane/metadata/android/en-GB/short_description.txt
@@ -0,0 +1 @@
+Treat your life like a game to stay motivated and organized!
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-GB/title.txt b/fastlane/metadata/android/en-GB/title.txt
new file mode 100644
index 0000000000..912febaf47
--- /dev/null
+++ b/fastlane/metadata/android/en-GB/title.txt
@@ -0,0 +1 @@
+Habitica: Gamify Your Tasks
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-GB/video.txt b/fastlane/metadata/android/en-GB/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
index 57ae3e75fd..f6ba1a6c62 100644
--- a/fastlane/metadata/android/en-US/full_description.txt
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -1,24 +1,38 @@
Habitica is a free habit-building and productivity app that uses retro RPG elements to gamify your tasks and goals.
Use Habitica to help with ADHD, self care, New Year’s resolutions, household chores, work tasks, creative projects, fitness goals, back-to-school routines, and more!
+
How it works:
Create an avatar then add tasks, chores, or goals you’d like to work on. When you do something in real life, check it off in the app and receive gold, experience, and items that can be used in-game!
+
Features:
-• Automatically repeating tasks scheduled for your daily, weekly, or monthly routines
-• Flexible habit tracker for tasks you want to do multiple times a day or only once in awhile
-• Traditional to do list for tasks that only need to be done once
-• Color coded tasks and streak counters help you see how you’re doing at a glance
-• Leveling system to visualize your overall progress
-• Tons of collectable gear and pets to suit your personal style
-• Inclusive avatar customizations: wheelchairs, hair styles, skin tones, and more
-• Regular content releases and seasonal events to keep things fresh
-• Parties let you team up with friends for extra accountability and battle fierce foes by completing tasks
-• Challenges offer shared task lists you can add to your personal tasks
-• Guilds let you connect with others that share your interests and goals
-• Reminders and widgets to help keep you on track
-• Customizable color themes with dark and light mode
-• Syncing across devices
-• Brand new WearOS watch app available in version 4.0!
+ • Automatically repeating tasks scheduled for your daily, weekly, or monthly routines
+ • Flexible habit tracker for tasks you want to do multiple times a day or only once in awhile
+ • Traditional to do list for tasks that only need to be done once
+ • Color coded tasks and streak counters help you see how you’re doing at a glance
+ • Leveling system to visualize your overall progress
+ • Tons of collectable gear and pets to suit your personal style
+ • Inclusive avatar customizations: wheelchairs, hair styles, skin tones, and more
+ • Regular content releases and seasonal events to keep things fresh
+ • Parties let you team up with friends for extra accountability and battle fierce foes by completing tasks
+ • Challenges offer shared task lists you can add to your personal tasks
+ • Reminders and widgets to help keep you on track
+ • Customizable color themes with dark and light mode
+ • Syncing across devices
+
+
+Want even more flexibility to take your tasks on the go? We have a Wear OS app on the watch!
+
+Wear OS features:
+ • View, create, and complete Habits, Dailies, and To do’s
+ • Receive rewards for your efforts with experience, food, eggs, and potions
+ • Track your stats with dynamic progress bars
+ • Show off your stunning pixel avatar on the watch face
+
+
—
-Habitica is an open-source app run by a small team that’s made better by the work of volunteers who contribute pixel art, translations, bug fixes, and more. If you’d like to contribute, reach out!
-Community, privacy, and transparency are important to us. Your tasks are private and we don’t sell your personal data to third parties.
-If you have any questions, feel free to send feedback to admin@habitica.com! And if you enjoy our app, we would really appreciate it if you would leave us a review.
\ No newline at end of file
+
+
+Run by a small team, Habitica is an open-source app made better by contributors who create translations, bug fixes, and more. If you’d like to contribute, you can check out our GitHub or reach out for more information!
+We highly value community, privacy, and transparency. Rest assured, your tasks remain private and we never sell your personal data to third parties.
+Questions or feedback? Feel free to reach us at admin@habitica.com! If you’re enjoying Habitica, we’d be thrilled if you leave us a review.
+Start your journey towards productivity, download Habitica now!
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png
new file mode 100644
index 0000000000..2ff638e2ad
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
index 80f2a170c7..a5584f73de 100644
Binary files a/fastlane/metadata/android/en-US/images/icon.png and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png
new file mode 100644
index 0000000000..a394ae2e29
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png
new file mode 100644
index 0000000000..87b22bd754
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png
new file mode 100644
index 0000000000..1e5cc33b38
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png
new file mode 100644
index 0000000000..e19041299e
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png
new file mode 100644
index 0000000000..ac166274bf
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png
new file mode 100644
index 0000000000..53a853e13f
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.png
new file mode 100644
index 0000000000..01e44520a8
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/7_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/8_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/8_en-US.png
new file mode 100644
index 0000000000..640863030e
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/8_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/wearScreenshots/1_en-US.png b/fastlane/metadata/android/en-US/images/wearScreenshots/1_en-US.png
new file mode 100644
index 0000000000..08bae128d9
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/wearScreenshots/1_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/wearScreenshots/2_en-US.png b/fastlane/metadata/android/en-US/images/wearScreenshots/2_en-US.png
new file mode 100644
index 0000000000..27e3a0f283
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/wearScreenshots/2_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/wearScreenshots/3_en-US.png b/fastlane/metadata/android/en-US/images/wearScreenshots/3_en-US.png
new file mode 100644
index 0000000000..6a8a8e38b2
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/wearScreenshots/3_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/wearScreenshots/4_en-US.png b/fastlane/metadata/android/en-US/images/wearScreenshots/4_en-US.png
new file mode 100644
index 0000000000..831ae5c237
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/wearScreenshots/4_en-US.png differ
diff --git a/fastlane/metadata/android/en-US/images/wearScreenshots/5_en-US.png b/fastlane/metadata/android/en-US/images/wearScreenshots/5_en-US.png
new file mode 100644
index 0000000000..ccca1b931e
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/wearScreenshots/5_en-US.png differ
diff --git a/fastlane/metadata/android/es-419/full_description.txt b/fastlane/metadata/android/es-419/full_description.txt
new file mode 100644
index 0000000000..83d96637e8
--- /dev/null
+++ b/fastlane/metadata/android/es-419/full_description.txt
@@ -0,0 +1,28 @@
+¡Trata tu vida como un juego para mantenerte motivado y organizado! Habitica hace que cumplir objetivos sea entretenido. Escribe tus hábitos, tus metas diarias y tu lista de pendientes, y luego crea un avatar personalizado.
+
+¡Marca tus tareas para subir de nivel y desbloquear objetos tales como armaduras, mascotas, habilidades, e incluso misiones! Lucha contra monstruos con tus amigos para mantenerse responsables mutuamente frente a sus tareas, y usa tu oro para comprar recompensas en el juego, como equipamiento, o premios personalizados, como ver un episodio de tu serie favorita. Flexible, social y divertida, Habitica es la mejor manera de motivarte para lograr cualquier cosa.
+
+Características:
+ • Repetir automáticamente tareas programadas para sus rutinas diarias, semanales o mensuales
+ • Rastreador de hábitos flexible para tareas que desea realizar varias veces al día o solo de vez en cuando
+ • Lista de tareas tradicional para tareas que solo deben realizarse una vez
+ • Las tareas codificadas por colores y los contadores de rachas te ayudan a ver cómo te va de un vistazo
+ • Sistema de nivelación para visualizar tu progreso general
+ • Toneladas de equipo coleccionable y mascotas que se adaptan a tu estilo personal
+ • Personalizaciones de avatar inclusivas: sillas de ruedas, peinados, tonos de piel y más
+ • Lanzamientos de contenido periódicos y eventos de temporada para mantener las cosas frescas.
+ • Los grupos te permiten formar equipo con amigos para tener mayor responsabilidad y luchar contra enemigos feroces completando tareas.
+ • Los desafíos ofrecen listas de tareas compartidas que puedes agregar a tus tareas personales
+ • Recordatorios y widgets para ayudarte a mantenerte encaminado
+ • Temas de color personalizables con modo oscuro y claro
+ • Sincronización entre dispositivos
+
+¿Quiere aún más flexibilidad para realizar sus tareas mientras viaja? ¡Tenemos una aplicación Wear OS en el reloj!
+
+Use características del sistema operativo:
+ • Ver, crear y completar hábitos, diarios y tareas pendientes
+ • Recibe recompensas por tus esfuerzos con experiencia, comida, huevos y pociones.
+ • Realice un seguimiento de sus estadísticas con barras de progreso dinámicas
+ • Muestra tu impresionante avatar de píxeles en la esfera del reloj
+
+Si tienes alguna duda, ¡siéntete libre de comunicárnosla a mobile@habitica.com ! Y si te gusta nuestra aplicación, nos encantaría que nos dejaras un comentario.
\ No newline at end of file
diff --git a/fastlane/metadata/android/es-419/short_description.txt b/fastlane/metadata/android/es-419/short_description.txt
new file mode 100644
index 0000000000..2dc497dfc4
--- /dev/null
+++ b/fastlane/metadata/android/es-419/short_description.txt
@@ -0,0 +1 @@
+¡Trata tu vida como un juego para mantenerte motivado y organizado!
\ No newline at end of file
diff --git a/fastlane/metadata/android/es-419/title.txt b/fastlane/metadata/android/es-419/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/es-419/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/es-419/video.txt b/fastlane/metadata/android/es-419/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/es-ES/full_description.txt b/fastlane/metadata/android/es-ES/full_description.txt
index 3f2244fa8b..70dda1b96f 100644
--- a/fastlane/metadata/android/es-ES/full_description.txt
+++ b/fastlane/metadata/android/es-ES/full_description.txt
@@ -1 +1,28 @@
-¡La NUEVA aplicación de HabitRPG! Reescrita desde cero para ofrecer más funciones y una experiencia más agradable. Vive tu vida como si fuese un juego para motivarte y organizarte. Con Habitica, es fácil conseguir tus objetivos y divertirte al mismo tiempo. Introduce tus hábitos, tus objetivos diarios y tu lista de tareas pendientes, y crea tu propio personaje. Completa tareas para que tu personaje suba de nivel y desbloquea armaduras, mascotas, habilidades y hasta misiones, entre otras cosas. Combate monstruos con tus amigos: si jugáis juntos, os resultará más fácil ser responsables. Usa el oro para comprar recompensas dentro del juego, como equipamiento o recompensas personalizadas: por ejemplo, ver un episodio de tu serie favorita. Habitica es flexible, social y divertida. Por eso es la mejor manera de motivarte y conseguir cualquier objetivo. Actualmente estamos en fase beta, así que puedes enviarnos tus comentarios a mobile@habitica.com. Y si te gusta nuestra aplicación, te agradeceríamos que la valoraras.
\ No newline at end of file
+¡Convierte tu vida en un juego para mantenerte motivado y organizado! Habitica hace sencillo divertirse mientras cumples tus objetivos. Crea tu propio personaje personalizado, introduce tus hábitos, tus tareas diarias y tu lista de tareas pendientes.
+
+¡Completa tus tareas para subir de nivel a tu personaje y desbloquear cosas como armaduras, mascotas, habilidades o incluso misiones! Combate monstruos con tus amigos para motivaros mutuamente a ser más responsables y gasta tu oro en recompensas del juego, como equipamiento o premios personalizados, o de la vida real, como ver un episodio de tu serie favorita. Flexible, social y divertido, Habitica es la mejor forma de motivarte para completar cualquier cosa.
+
+Características:
+ • Repetir automáticamente tareas programadas para sus rutinas diarias, semanales o mensuales
+ • Rastreador de hábitos flexible para tareas que desea realizar varias veces al día o solo de vez en cuando
+ • Lista de tareas tradicional para tareas que solo deben realizarse una vez
+ • Las tareas codificadas por colores y los contadores de rachas te ayudan a ver cómo te va de un vistazo
+ • Sistema de nivelación para visualizar tu progreso general
+ • Toneladas de equipo coleccionable y mascotas que se adaptan a tu estilo personal
+ • Personalizaciones de avatar inclusivas: sillas de ruedas, peinados, tonos de piel y más
+ • Lanzamientos de contenido periódicos y eventos de temporada para mantener las cosas frescas.
+ • Los grupos te permiten formar equipo con amigos para tener mayor responsabilidad y luchar contra enemigos feroces completando tareas.
+ • Los desafíos ofrecen listas de tareas compartidas que puedes agregar a tus tareas personales
+ • Recordatorios y widgets para ayudarte a mantenerte encaminado
+ • Temas de color personalizables con modo oscuro y claro
+ • Sincronización entre dispositivos
+
+¿Quiere aún más flexibilidad para realizar sus tareas mientras viaja? ¡Tenemos una aplicación Wear OS en el reloj!
+
+Use características del sistema operativo:
+ • Ver, crear y completar hábitos, diarios y tareas pendientes
+ • Recibe recompensas por tus esfuerzos con experiencia, comida, huevos y pociones.
+ • Realice un seguimiento de sus estadísticas con barras de progreso dinámicas
+ • Muestra tu impresionante avatar de píxeles en la esfera del reloj
+
+Si tienes cualquier sugerencia, siéntete libre de hacérnosla llegar a "mobile@habitica.com". Y si te gusta nuestra aplicación, ¡agradeceríamos enormemente que nos dejases tu valoración!
\ No newline at end of file
diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt
index 1a5f78b255..0b3f87faed 100644
--- a/fastlane/metadata/android/fr-FR/full_description.txt
+++ b/fastlane/metadata/android/fr-FR/full_description.txt
@@ -1,3 +1,29 @@
-Gérez votre vie comme un jeu pour garder la motivation et l'organisation ! Habitica rend simple le fait de s'amuser en accomplissant ses tâches.
-Intégrez vos habitudes, vos objectifs quotidiens, et votre liste de tâches, puis créez-vous un avatar personnalisé. Validez vos tâches pour gagner des niveau et débloquer des fonctionnalités comme l'armure, les familiers, les compétences et même les quêtes ! Combattez les monstres avec vos amis pour vous responsabiliser, et utilisez votre or pour acheter des récompenses du jeu, comme de l'équipement, ou des récompenses personnalisées comme regarder un épisode de votre série préférée. Flexible, social et amusant, Habitica est la meilleure manière de vous motiver à accomplir vos objectifs.
-Si vous avez la moindre question, n'hésitez pas à envoyer un message à mobile@habitica.com ! Et si vous appréciez notre application, nous vous serions reconnaissant de bien vouloir nous laisser un avis
\ No newline at end of file
+Gérez votre vie comme un jeu pour garder la motivation et l'organisation ! Habitica rend simple le fait de s'amuser en accomplissant ses tâches.Intégrez vos habitudes, vos objectifs quotidiens, et votre liste de tâches, puis créez-vous un avatar personnalisé.
+
+Validez vos tâches pour gagner des niveau et débloquer des fonctionnalités comme l'armure, les familiers, les compétences et même les quêtes ! Combattez les monstres avec vos amis pour vous responsabiliser, et utilisez votre or pour acheter des récompenses du jeu, comme de l'équipement, ou des récompenses personnalisées comme regarder un épisode de votre série préférée. Flexible, social et amusant, Habitica est la meilleure manière de vous motiver à accomplir vos objectifs.
+
+Caractéristiques:
+ • Répétition automatique des tâches programmées pour vos routines quotidiennes, hebdomadaires ou mensuelles
+ • Suivi des habitudes flexible pour les tâches que vous souhaitez effectuer plusieurs fois par jour ou seulement de temps en temps
+ • Liste de tâches traditionnelle pour les tâches qui ne doivent être effectuées qu'une seule fois.
+ • Les tâches codées par couleur et les compteurs de séquences vous aident à voir comment vous vous en sortez en un coup d'œil.
+ • Système de mise à niveau pour visualiser votre progression globale
+ • Des tonnes d'équipements à collectionner et d'animaux de compagnie adaptés à votre style personnel
+ • Personnalisations d'avatar incluses : fauteuils roulants, coiffures, tons de peau, etc.
+ • Sorties de contenu régulières et événements saisonniers pour garder les choses à jour
+ • Les groupes vous permettent de faire équipe avec des amis pour plus de responsabilité et de combattre des ennemis féroces en accomplissant des tâches
+ • Les défis proposent des listes de tâches partagées que vous pouvez ajouter à vos tâches personnelles.
+ • Rappels et widgets pour vous aider à rester sur la bonne voie
+ • Thèmes de couleurs personnalisables avec modes sombre et clair
+ • Synchronisation entre appareils
+
+Vous souhaitez encore plus de flexibilité pour effectuer vos tâches en déplacement ? Nous avons une application Wear OS sur la montre !
+
+Fonctionnalités du système d'exploitation Wear :
+ • Afficher, créer et compléter les habitudes, les quotidiens et les tâches à faire
+ • Recevez des récompenses pour vos efforts avec de l'expérience, de la nourriture, des œufs et des potions.
+ • Suivez vos statistiques avec des barres de progression dynamiques
+ • Montrez votre superbe avatar en pixels sur le cadran de la montre
+
+
+Si vous avez la moindre question, n'hésitez pas à envoyer un message à mobile@habitica.com ! Et si vous appréciez notre application, nous vous serions reconnaissant de bien vouloir nous laisser un avis.
\ No newline at end of file
diff --git a/fastlane/metadata/android/hr/full_description.txt b/fastlane/metadata/android/hr/full_description.txt
new file mode 100644
index 0000000000..2f2af805a6
--- /dev/null
+++ b/fastlane/metadata/android/hr/full_description.txt
@@ -0,0 +1,28 @@
+Pretvori život u igru da ostaneš motiviran/a i organiziran/a! Habitica ti jednostavno omogućuje da se zabaviš i istovremeno ostvaruješ svoje ciljeve. Unesi svoje Navike, svoje Svakodnevne ciljeve i svoj popis Zadataka, a potom stvori svoj osobni avatar.
+
+Križaj zadatke kako bi unaprijedio/la svog avatara i otključao/la mogućnosti poput oklopa, ljubimaca, vještina, pa čak i pustolovina! Bori se protiv čudovišta s prijateljima kako biste ostali odgovorni i troši svoje zlatnike na nagrade u igri poput opreme, ili pak na prilagođene nagrade poput gledanja epizode svoje omiljene TV serije. Fleksibilna, društvena i zabavna, Habitica je savršen način da se motiviraš za postizanje bilo čega.
+
+Značajke:
+ • Automatsko ponavljanje zadataka zakazanih za vaše dnevne, tjedne ili mjesečne rutine
+ • Fleksibilni alat za praćenje navika za zadatke koje želite obavljati više puta dnevno ili samo jednom u neko vrijeme
+ • Tradicionalni popis zadataka koji se moraju obaviti samo jednom
+ • Zadaci označeni bojama i brojači nizova pomažu vam da na prvi pogled vidite kako napredujete
+ • Sustav niveliranja za vizualizaciju vašeg ukupnog napretka
+ • Tone kolekcionarske opreme i kućnih ljubimaca koji odgovaraju vašem osobnom stilu
+ • Uključive prilagodbe avatara: invalidska kolica, frizure, tonovi kože i više
+ • Redovita izdanja sadržaja i sezonski događaji kako bi stvari bile svježe
+ • Zabave vam omogućuju da se udružite s prijateljima za dodatnu odgovornost i borite se s žestokim neprijateljima ispunjavanjem zadataka
+ • Izazovi nude zajedničke popise zadataka koje možete dodati svojim osobnim zadacima
+ • Podsjetnici i widgeti koji će vam pomoći da ostanete na pravom putu
+ • Prilagodljive teme boja s tamnim i svijetlim načinom rada
+ • Sinkronizacija između uređaja
+
+Želite još veću fleksibilnost za izvršavanje zadataka u pokretu? Imamo Wear OS aplikaciju na satu!
+
+Značajke Wear OS-a:
+ • Pregledajte, izradite i dovršite Navike, Dnevnike i Obaveze
+ • Primite nagrade za svoj trud iskustvom, hranom, jajima i napitcima
+ • Pratite svoju statistiku pomoću dinamičkih traka napretka
+ • Pokažite svoj zapanjujući pikselni avatar na brojčaniku sata
+
+Ako imaš ikakva pitanja, slobodno nam se javi na mobile@habitica.com! I ako uživaš u našoj aplikaciji, stvarno bi znam značilo ako bi nam ostavio/la ocjenu.
\ No newline at end of file
diff --git a/fastlane/metadata/android/hr/short_description.txt b/fastlane/metadata/android/hr/short_description.txt
new file mode 100644
index 0000000000..097f05fdd7
--- /dev/null
+++ b/fastlane/metadata/android/hr/short_description.txt
@@ -0,0 +1 @@
+Pretvori život u igru da ostaneš motiviran/a i organiziran/a!
\ No newline at end of file
diff --git a/fastlane/metadata/android/hr/title.txt b/fastlane/metadata/android/hr/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/hr/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/hr/video.txt b/fastlane/metadata/android/hr/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/id/full_description.txt b/fastlane/metadata/android/id/full_description.txt
new file mode 100644
index 0000000000..62e78bedb7
--- /dev/null
+++ b/fastlane/metadata/android/id/full_description.txt
@@ -0,0 +1,28 @@
+Perlakukan hidupmu seperti permainan agar tetap termotivasi dan terorganisir! Habitica membantumu untuk bersenang-senang dalam mencapai tujuan. Masukkan Kebiasaan, sasaran Harian dan To Do kamu, lalu buat avatarmu sendiri.
+
+Centang tugas-tugas untuk naik level dan buka fitur-fitur seperti perlengkapan, peliharaan, kemampuan, dan bahkan misi! Bertarung dengan monster bersama teman-teman supaya saling menyemangati, dan gunakan koin emas yang kamu punya untuk membeli hadiah di dalam game, seperti perlengkapan perang, atau hadiah yang kamu buat sendiri, misalnya menonton satu episode siaran TV kesukaanmu. Fleksibel, sosial, menyenangkan, Habitica sempurna untuk memotivasimu mencapai apapun.
+
+Fitur:
+ • Secara otomatis mengulangi tugas yang dijadwalkan untuk rutinitas harian, mingguan, atau bulanan Anda
+ • Pelacak kebiasaan fleksibel untuk tugas yang ingin Anda lakukan beberapa kali sehari atau hanya sesekali
+ • Daftar tugas tradisional untuk tugas-tugas yang hanya perlu dilakukan satu kali
+ • Tugas berkode warna dan penghitung coretan membantu Anda melihat kinerja Anda secara sekilas
+ • Sistem leveling untuk memvisualisasikan kemajuan Anda secara keseluruhan
+ • Banyak perlengkapan dan hewan peliharaan yang dapat dikoleksi untuk disesuaikan dengan gaya pribadi Anda
+ • Kustomisasi avatar yang inklusif: kursi roda, gaya rambut, warna kulit, dan banyak lagi
+ • Rilis konten rutin dan acara musiman untuk menjaga semuanya tetap segar
+ • Pesta memungkinkan Anda bekerja sama dengan teman untuk mendapatkan akuntabilitas ekstra dan melawan musuh sengit dengan menyelesaikan tugas
+ • Tantangan menawarkan daftar tugas bersama yang dapat Anda tambahkan ke tugas pribadi Anda
+ • Pengingat dan widget untuk membantu Anda tetap pada jalurnya
+ • Tema warna yang dapat disesuaikan dengan mode gelap dan terang
+ • Sinkronisasi antar perangkat
+
+Ingin lebih banyak fleksibilitas untuk melakukan tugas Anda saat bepergian? Kami memiliki aplikasi Wear OS di jam tangan!
+
+Fitur Wear OS:
+ • Melihat, membuat, dan menyelesaikan Kebiasaan, Harian, dan Agenda
+ • Terima imbalan atas usaha Anda berupa pengalaman, makanan, telur, dan ramuan
+ • Lacak statistik Anda dengan bilah kemajuan dinamis
+ • Pamerkan avatar piksel Anda yang menakjubkan di tampilan jam
+
+Kalau kamu punya pertanyaan maupun saran, silakan kirim ke mobile@habitica.com! Dan jika kamu menyukai aplikasi ini, kami akan sangat menghargai ulasanmu.
\ No newline at end of file
diff --git a/fastlane/metadata/android/id/short_description.txt b/fastlane/metadata/android/id/short_description.txt
new file mode 100644
index 0000000000..1624215349
--- /dev/null
+++ b/fastlane/metadata/android/id/short_description.txt
@@ -0,0 +1 @@
+Buat hidupmu menjadi sebuah permainan agar tetap termotivasi dan terorganisir!
\ No newline at end of file
diff --git a/fastlane/metadata/android/id/title.txt b/fastlane/metadata/android/id/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/id/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/id/video.txt b/fastlane/metadata/android/id/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/it-IT/full_description.txt b/fastlane/metadata/android/it-IT/full_description.txt
index 0750bb7bed..0b30e41394 100644
--- a/fastlane/metadata/android/it-IT/full_description.txt
+++ b/fastlane/metadata/android/it-IT/full_description.txt
@@ -1,3 +1,28 @@
-Trasforma la tua vita in un gioco di ruolo per essere organizzato e motivato! Con Habitica raggiungere i propri obiettivi non è mai stato così divertente.
-Imposta le tue abitudini (Habit), i tuoi obiettivi giornalieri (Daily) e la tua lista di cose da fare (To-Do), poi crea il tuo avatar personalizzato. Completa le attività per far salire di livello il tuo avatar e sbloccare funzionalità come armature, animali, abilità e persino missioni! Combatti i mostri con i tuoi amici per aiutarvi a vicenda ad essere responsabili, e usa il tuo oro per ricompense in-game come pezzi di equipaggiamento, o ricompense personalizzate, ad esempio guardare un episodio della tua serie TV preferita. Flessibile, sociale e soprattutto divertente, Habitica è il modo perfetto per motivarti a fare qualsiasi cosa.
+Trasforma la tua vita in un gioco di ruolo per essere organizzato e motivato! Con Habitica raggiungere i propri obiettivi non è mai stato così divertente. Imposta le tue abitudini (Habit), i tuoi obiettivi giornalieri (Daily) e la tua lista di cose da fare (To-Do), poi crea il tuo avatar personalizzato.
+
+Completa le attività per far salire di livello il tuo avatar e sbloccare funzionalità come armature, animali, abilità e persino missioni! Combatti i mostri con i tuoi amici per aiutarvi a vicenda ad essere responsabili, e usa il tuo oro per ricompense in-game come pezzi di equipaggiamento, o ricompense personalizzate, ad esempio guardare un episodio della tua serie TV preferita. Flessibile, sociale e soprattutto divertente, Habitica è il modo perfetto per motivarti a fare qualsiasi cosa.
+
+Caratteristiche:
+ • Ripetizione automatica delle attività pianificate per la routine quotidiana, settimanale o mensile
+ • Monitoraggio flessibile delle abitudini per le attività che desideri svolgere più volte al giorno o solo una volta ogni tanto
+ • Tradizionale elenco delle cose da fare per le attività che devono essere eseguite una sola volta
+ • Le attività codificate a colori e i contatori di serie ti aiutano a vedere come stai andando a colpo d'occhio
+ • Sistema di livellamento per visualizzare i tuoi progressi complessivi
+ • Tonnellate di attrezzi da collezione e animali domestici adatti al tuo stile personale
+ • Personalizzazioni avatar incluse: sedie a rotelle, acconciature, tonalità della pelle e altro ancora
+ • Rilasci regolari di contenuti ed eventi stagionali per mantenere le cose fresche
+ • I gruppi ti consentono di unirti agli amici per una maggiore responsabilità e combattere nemici feroci completando le attività
+ • Le sfide offrono elenchi di attività condivisi che puoi aggiungere alle tue attività personali
+ • Promemoria e widget per aiutarti a restare aggiornato
+ • Temi di colore personalizzabili con modalità scura e chiara
+ • Sincronizzazione tra dispositivi
+
+Desideri ancora più flessibilità per svolgere le tue attività in movimento? Abbiamo un'app Wear OS sull'orologio!
+
+Indossa le funzionalità del sistema operativo:
+ • Visualizza, crea e completa abitudini, quotidianità e cose da fare
+ • Ricevi ricompense per i tuoi sforzi con esperienza, cibo, uova e pozioni
+ • Tieni traccia delle tue statistiche con barre di avanzamento dinamiche
+ • Mostra il tuo straordinario avatar pixel sul quadrante
+
Se avete una qualsiasi domanda o dei suggerimenti, sentitevi liberi di scrivere a mobile@habitica.com! E se vi piace la nostra app, saremmo davvero felici se ci lasciaste una recensione.
\ No newline at end of file
diff --git a/fastlane/metadata/android/iw-IL/full_description.txt b/fastlane/metadata/android/iw-IL/full_description.txt
new file mode 100644
index 0000000000..0188414af0
--- /dev/null
+++ b/fastlane/metadata/android/iw-IL/full_description.txt
@@ -0,0 +1,26 @@
+החיים האמיתיים מאורגנים בעזרת משחק ומוטיבציה בשמיים! בטיקה הופכת את ההגעה ליעדים לפשוטה ומהנה יותר. אתה יכול למלא את ההרגלים שלך, את המטלות היומיומיות שלך ואת רשימת המטלות שלך, וגם ליצור דמות משלך. הדמות שלך יכולה לעלות רמה על ידי סימון משימות שהושלמו ולפתוח דברים כמו שריון, חיות מחמד, כישורים ואפילו הרפתקאות! אתה יכול להילחם במפלצות עם חברים ולשמור על קשר, ואתה יכול גם להשתמש במטבעות הזהב כדי לקנות תגמולים במשחק, כמו ציוד או תגמולים מותאמים אישית, כמו צפייה בפרק בסדרת הטלוויזיה האהובה עליך. גמישות, חברותיות וכיף הופכים את הבטיק לדרך המושלמת להניע את עצמך להשיג מטרות בחיים.
+
+מאפיינים:
+ • חזרה אוטומטית על משימות שתוזמנו לשגרה היומית, השבועית או החודשית שלך
+ • מעקב הרגלים גמיש למשימות שאתה רוצה לבצע מספר פעמים ביום או רק פעם בכמה זמן
+ • רשימת מטלות מסורתית למשימות שצריך לבצע פעם אחת בלבד
+ • משימות מקודדות צבע ומדידות פסים עוזרות לך לראות מה שלומך במבט חטוף
+ • מערכת פילוס כדי לדמיין את ההתקדמות הכוללת שלך
+ • טונות של ציוד אספנות וחיות מחמד שיתאימו לסגנון האישי שלך
+ • התאמות אישיות כוללות של אווטאר: כסאות גלגלים, סגנונות שיער, גווני עור ועוד
+ • פרסומי תוכן קבועים ואירועים עונתיים כדי לשמור על טריות
+ • מסיבות מאפשרות לך לחבור לחברים לקבלת אחריות נוספת ולהילחם באויבים עזים על ידי השלמת משימות
+ • אתגרים מציעים רשימות משימות משותפות שתוכל להוסיף למשימות האישיות שלך
+ • תזכורות ווידג'טים שיעזרו לך לשמור על המסלול
+ • ערכות נושא צבע הניתנות להתאמה אישית עם מצב כהה ואור
+ • סנכרון בין מכשירים
+
+רוצה עוד יותר גמישות לקחת את המשימות שלך בדרכים? יש לנו אפליקציית Wear OS על השעון!
+
+תכונות Wear OS:
+ • הצג, צור והשלם הרגלים, דיווחים יומיים ומשימות לביצוע
+ • קבל תגמולים על המאמצים שלך עם ניסיון, אוכל, ביצים ושיקויים
+ • עקוב אחר הנתונים הסטטיסטיים שלך עם פסי התקדמות דינמיים
+ • הצג את אווטאר הפיקסלים המהמם שלך על פני השעון
+
+אם יש לך שאלות, אנו מזמינים אותך לשלוח משוב לכתובת mobile@habitica.com! ואם נהנית מהאפליקציה שלנו, נשמח לקבל ממך חוות דעת.
\ No newline at end of file
diff --git a/fastlane/metadata/android/iw-IL/short_description.txt b/fastlane/metadata/android/iw-IL/short_description.txt
new file mode 100644
index 0000000000..a55821c7b7
--- /dev/null
+++ b/fastlane/metadata/android/iw-IL/short_description.txt
@@ -0,0 +1 @@
+החיים האמיתיים מאורגנים בעזרת משחק והמוטיבציה בשחקים!
\ No newline at end of file
diff --git a/fastlane/metadata/android/iw-IL/title.txt b/fastlane/metadata/android/iw-IL/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/iw-IL/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/iw-IL/video.txt b/fastlane/metadata/android/iw-IL/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt
index d14fa0e4c9..239db24856 100644
--- a/fastlane/metadata/android/ja-JP/full_description.txt
+++ b/fastlane/metadata/android/ja-JP/full_description.txt
@@ -1,5 +1,28 @@
-自分の人生をゲーム化して、やる気の維持とやるべきことの整理をしましょう!Habitica は目標達成を楽しく簡単にします。
+自分の人生をゲーム化して、やる気の維持とやるべきことの整理をしましょう!Habitica は目標達成を楽しく簡単にします。 習慣、毎日の目標、そしてTo-Do の一覧を入力し、お好みのアバターをつくりましょう。
- 習慣、毎日の目標、そしてTo-Do の一覧を入力し、お好みのアバターをつくりましょう。タスクをこなして、レベルを上げていくと、防具、ペット、スキルやクエストなどの機能をアンロックしていくこともできます! 責任をもちながら友達といっしょにモンスターと戦い、ゲーム内でゴールドを稼いで、アバターの装備や、「好きなテレビ番組の 1 話分」などの自分好みのごほうびを買うこともできます。柔軟で、ソーシャルで、楽しい――Habitica は、あなたがご自身の目標を達成するのを励ますカンペキな方法です。
+タスクをこなして、レベルを上げていくと、防具、ペット、スキルやクエストなどの機能をアンロックしていくこともできます! 責任をもちながら友達といっしょにモンスターと戦い、ゲーム内でゴールドを稼いで、アバターの装備や、「好きなテレビ番組の 1 話分」などの自分好みのごほうびを買うこともできます。柔軟で、ソーシャルで、楽しい――Habitica は、あなたがご自身の目標を達成するのを励ますカンペキな方法です。
- 質問があったら、ぜひお気軽に mobile@habitica.com あてにフィードバックをお寄せください!そしてもしこのアプリをお楽しみいただけたなら、レビューを残していただけると、たいへんうれしいです。
\ No newline at end of file
+特徴:
+ • 毎日、毎週、または毎月のルーチンとしてスケジュールされたタスクを自動的に繰り返す
+ • 1 日に複数回、またはたまにだけ実行したいタスクに対応する柔軟な習慣トラッカー
+ • 一度だけ実行する必要があるタスクの従来の To Do リスト
+ • 色分けされたタスクとストリークカウンターにより、自分の進捗状況が一目でわかります
+ • 全体的な進捗状況を視覚化するレベリングシステム
+ • 自分のスタイルに合った収集可能な装備やペットがたくさん
+ • アバターの包括的なカスタマイズ: 車椅子、ヘアスタイル、肌の色など
+ • コンテンツを定期的にリリースし、季節ごとのイベントを開催して常に最新の状態を保ちます。
+ • パーティーを使用すると、友達とチームを組んでさらなる責任を負い、タスクを完了することで激しい敵と戦うことができます
+ • チャレンジでは、個人のタスクに追加できる共有タスク リストを提供します
+ • 順調に進むためのリマインダーとウィジェット
+ • ダークモードとライトモードによるカスタマイズ可能なカラーテーマ
+ • デバイス間の同期
+
+外出先でもタスクを実行できる柔軟性をさらに高めたいですか?時計にはWear OSアプリが搭載されています。
+
+Wear OS の機能:
+ • 習慣、日課、やるべきことを表示、作成、完了する
+ • 経験、食べ物、卵、ポーションなどの努力に対する報酬を受け取りましょう
+ • 動的なプログレスバーで統計を追跡
+ • ウォッチフェイス上で美しいピクセルアバターを披露
+
+質問があったら、ぜひお気軽に mobile@habitica.com あてにフィードバックをお寄せください!そしてもしこのアプリをお楽しみいただけたなら、レビューを残していただけると、たいへんうれしいです。
\ No newline at end of file
diff --git a/fastlane/metadata/android/ko-KR/full_description.txt b/fastlane/metadata/android/ko-KR/full_description.txt
index 99cd0b7d43..2c89585eca 100644
--- a/fastlane/metadata/android/ko-KR/full_description.txt
+++ b/fastlane/metadata/android/ko-KR/full_description.txt
@@ -1,5 +1,28 @@
-동기부여와 체계적인 삶을 위해 당신의 인생을 게임처럼 다루세요! Habitica는 목표를 달성하는 것을 재미있게 만들어줍니다.
+동기부여와 체계적인 삶을 위해 당신의 인생을 게임처럼 다루세요! Habitica는 목표를 달성하는 것을 재미있게 만들어줍니다. 습관, 하루 목표, 할일 목록을 입력한 다음 자신만의 아바타를 만드세요.
-습관, 하루 목표, 할일 목록을 입력한 다음 자신만의 아바타를 만드세요. 할일을 확인하여 아바타의 레벨을 올리고 갑옷, 애완 동물, 스킬 및 퀘스트와 같은 기능을 잠금 해제하세요! 친구들과 함께 각자의 할일을 함으로써 몬스터와 대항하고, 골드로 장비 등의 보상을 획득하세요. TV쇼를 볼 수 있는 보상과 같은 자신만의 보상도 가능합니다. 소셜과 연계된 재밌는 Habitica는 무엇이든 성취 할 수 있도록 동기를 부여하는 완벽한 방법입니다.
+할일을 확인하여 아바타의 레벨을 올리고 갑옷, 애완 동물, 스킬 및 퀘스트와 같은 기능을 잠금 해제하세요! 친구들과 함께 각자의 할일을 함으로써 몬스터와 대항하고, 골드로 장비 등의 보상을 획득하세요. TV쇼를 볼 수 있는 보상과 같은 자신만의 보상도 가능합니다. 소셜과 연계된 재밌는 Habitica는 무엇이든 성취 할 수 있도록 동기를 부여하는 완벽한 방법입니다.
+
+특징:
+ • 매일, 매주 또는 매월 루틴에 대해 예약된 작업을 자동으로 반복합니다.
+ • 하루에 여러 번 또는 가끔씩만 수행하려는 작업을 위한 유연한 습관 추적기
+ • 한 번만 수행하면 되는 작업에 대한 전통적인 작업 목록
+ • 색상으로 구분된 작업과 연속 카운터를 통해 자신의 진행 상황을 한눈에 확인할 수 있습니다.
+ • 전반적인 진행 상황을 시각화하는 레벨링 시스템
+ • 개인 스타일에 맞는 수많은 수집 가능한 장비와 애완동물
+ • 포괄적인 아바타 사용자 정의: 휠체어, 머리 스타일, 피부색 등
+ • 정기적인 콘텐츠 출시와 시즌 이벤트를 통해 최신 정보 유지
+ • 파티를 통해 친구와 팀을 이루어 추가 책임감을 갖고 작업을 완료하여 치열한 적과 싸울 수 있습니다.
+ • 챌린지는 개인 작업에 추가할 수 있는 공유 작업 목록을 제공합니다.
+ • 순조롭게 진행하는 데 도움이 되는 알림 및 위젯
+ • 어두운 모드와 밝은 모드로 사용자 정의 가능한 색상 테마
+ • 여러 기기에서 동기화
+
+이동 중에도 작업을 수행할 수 있는 더 많은 유연성을 원하십니까? 시계에 Wear OS 앱이 있습니다!
+
+Wear OS 기능:
+ • 습관, 일일 일, 할 일을 보고, 만들고, 완료합니다.
+ • 경험치, 음식, 계란, 물약으로 노력에 대한 보상을 받으세요.
+ • 동적 진행률 표시줄로 통계를 추적하세요.
+ • 시계 모드에서 멋진 픽셀 아바타를 뽐내세요.
질문이 있으시면 mobile@habitica.com으로 피드백을 보내주세요! 앱이 재미있다면 리뷰를 남겨 주시면 감사하겠습니다.
\ No newline at end of file
diff --git a/fastlane/metadata/android/nl-NL/full_description.txt b/fastlane/metadata/android/nl-NL/full_description.txt
index 6d1292f08a..7eb4bf521d 100644
--- a/fastlane/metadata/android/nl-NL/full_description.txt
+++ b/fastlane/metadata/android/nl-NL/full_description.txt
@@ -1,3 +1,28 @@
-Behandel je leven als een spel om gemotiveerd en georganiseerd te blijven! Habitica houdt het simpel om plezier te maken terwijl je doelen bereikt.
-Voer je gewoontes, dagelijkse taken en to-do's toe en maak dan een eigen avatar. Vink taken af om je avatar sterker te maken en functies vrij te spelen zoals uitrusting, huisdieren, vaardigheden en zelfs queesten! Vecht tegen monsters met vrienden om samen verantwoordelijk te blijven en gebruik je goud voor beloningen, zoals uitrusting of kies je eigen beloning, zoals een aflevering van je favoriete serie bekijken. Flexibel, sociaal en plezant, Habitica is de perfecte manier om jezelf te motiveren om alles te bereiken.
+Behandel je leven als een spel om gemotiveerd en georganiseerd te blijven! Habitica houdt het simpel om plezier te maken terwijl je doelen bereikt. Voer je gewoontes, dagelijkse taken en to-do's toe en maak dan een eigen avatar.
+
+Vink taken af om je avatar sterker te maken en functies vrij te spelen zoals uitrusting, huisdieren, vaardigheden en zelfs queesten! Vecht tegen monsters met vrienden om samen verantwoordelijk te blijven en gebruik je goud voor beloningen, zoals uitrusting of kies je eigen beloning, zoals een aflevering van je favoriete serie bekijken. Flexibel, sociaal en plezant, Habitica is de perfecte manier om jezelf te motiveren om alles te bereiken.
+
+Functies:
+ • Automatisch herhalen van taken die zijn gepland voor uw dagelijkse, wekelijkse of maandelijkse routines
+ • Flexibele gewoonte-tracker voor taken die u meerdere keren per dag of slechts af en toe wilt uitvoeren
+ • Traditionele to do-lijst voor taken die maar één keer gedaan hoeven te worden
+ • Kleurgecodeerde taken en streaktellers zorgen ervoor dat u in één oogopslag kunt zien hoe het met u gaat
+ • Niveausysteem om uw algehele voortgang te visualiseren
+ • Tal van verzamelbare uitrustingen en huisdieren die bij jouw persoonlijke stijl passen
+ • Inclusief avataraanpassingen: rolstoelen, haarstijlen, huidtinten en meer
+ • Regelmatige releases van inhoud en seizoensevenementen om de zaken actueel te houden
+ • Met feesten kun je samenwerken met vrienden voor extra verantwoordelijkheid en de strijd aangaan met felle vijanden door taken te voltooien
+ • Uitdagingen bieden gedeelde takenlijsten die u aan uw persoonlijke taken kunt toevoegen
+ • Herinneringen en widgets om u op het goede spoor te houden
+ • Aanpasbare kleurthema's met donkere en lichte modus
+ • Synchroniseren tussen apparaten
+
+Wilt u nog meer flexibiliteit om uw taken onderweg uit te voeren? We hebben een Wear OS-app op het horloge!
+
+Wear OS-functies:
+ • Gewoontes, dagelijkse taken en taken bekijken, creëren en voltooien
+ • Ontvang beloningen voor je inspanningen met ervaring, eten, eieren en drankjes
+ • Houd uw statistieken bij met dynamische voortgangsbalken
+ • Laat je verbluffende pixelavatar zien op de wijzerplaat
+
Als je vragen hebt kan je ze stellen bij mobile@habitica.com! En als je de app leuk vindt, waarderen we het als je ons een recensie nalaat.
\ No newline at end of file
diff --git a/fastlane/metadata/android/pl-PL/full_description.txt b/fastlane/metadata/android/pl-PL/full_description.txt
index afeecd3257..402755576a 100644
--- a/fastlane/metadata/android/pl-PL/full_description.txt
+++ b/fastlane/metadata/android/pl-PL/full_description.txt
@@ -1,7 +1,26 @@
-Nowa apka od HabitRPG! Przepisana od zera, bardziej przyjazna i z nowymi funkcjami.
+Traktuj swoje życie jak grę aby być zmotywowanym i zorganizowanym! Dzięki Habitica osiąganie celów jest fajne. Wprowadź swoje Nawyki, Codzienne oraz Do-zrobienia, a potem stwórz swojego awatara. Odhaczaj zadania by zwiększać poziom swojego awatara i odblokowywać takie opcje jak zbroja, chowańce, umiejętności a nawet misje! Zwalczaj potwory z przyjaciółmi by motywować siebie nawzajem i wydawaj złoto na wbudowane nagrody takie jak zbroja lub stworzone przez siebie, takie jak obejrzenie odcinka ulubionego serialu. Elastyczna, społecznościowa i zabawna - Habitica jest idealnym sposobem na zmotywowanie siebie do osiągnięcia celów.
-Traktuj życie jako grę, będziesz zmotywowany i zorganizowany.
+Cechy:
+ • Automatyczne powtarzanie zadań zaplanowanych w ramach codziennych, cotygodniowych lub miesięcznych rutyn
+ • Elastyczny moduł śledzenia nawyków do zadań, które chcesz wykonywać kilka razy dziennie lub tylko raz na jakiś czas
+ • Tradycyjna lista rzeczy do zrobienia zawierająca zadania, które należy wykonać tylko raz
+ • Oznaczone kolorami zadania i liczniki serii pozwalają szybko sprawdzić, jak sobie radzisz
+ • System poziomowania umożliwiający wizualizację ogólnego postępu
+ • Mnóstwo kolekcjonerskiego sprzętu i zwierzątek, które pasują do Twojego osobistego stylu
+ • Możliwość dostosowania awatarów: wózki inwalidzkie, fryzury, odcienie skóry i inne
+ • Regularne publikacje treści i wydarzenia sezonowe zapewniające świeżość
+ • Strony umożliwiają współpracę z przyjaciółmi w celu uzyskania dodatkowej odpowiedzialności i walkę z zaciekłymi wrogami, wykonując zadania
+ • Wyzwania oferują wspólne listy zadań, które możesz dodać do swoich osobistych zadań
+ • Przypomnienia i widżety pomagające utrzymać się na właściwej drodze
+ • Konfigurowalne motywy kolorystyczne z trybem ciemnym i jasnym
+ • Synchronizacja między urządzeniami
-Wpisz swoje Nawyki, swoje Codzienne cele, swoją listę Do-Zrobienia i stwórz własnego awatara. Odhaczaj zadania aby levelować postać i odblokować opcje jak pancerz, zwierzaczki, zdolności a nawet misje! Walcz z potworami wespół z przyjaciółmi aby wzajemnie się podbudowywać, i zdobywać złoto oraz nagrody: ekwipunek, odznaczenia, bilet na odcinek Twojego ulubionego serialu. Przyjazny, społeczny i zabawny, HAbitica jest doskonałym sposobem by zmotywować się i dokończyć wszystko.
+Chcesz jeszcze większej elastyczności, aby móc wykonywać swoje zadania w drodze? Na zegarku mamy aplikację Wear OS!
-Obecnie mamy wersję beta, tak więc swobodnie zbesztaj nas przez mobile@habitica.com! A jeśli podoba ci się nasza apka, będziemy wdzięczni za pozostawienie opini.
\ No newline at end of file
+Funkcje systemu operacyjnego Wear:
+ • Przeglądaj, twórz i wykonuj nawyki, codzienne czynności i zadania do wykonania
+ • Otrzymuj nagrody za swoje wysiłki w postaci doświadczenia, jedzenia, jajek i mikstur
+ • Śledź swoje statystyki za pomocą dynamicznych pasków postępu
+ • Pochwal się swoim oszałamiającym pikselowym awatarem na tarczy zegarka
+
+W razie jakichkolwiek pytań pisz śmiało na mobile@habitica.com. A jeśli podoba Ci się nasza aplikacja, bylibyśmy naprawdę wdzięczni za opinię.
\ No newline at end of file
diff --git a/fastlane/metadata/android/pt-BR/full_description.txt b/fastlane/metadata/android/pt-BR/full_description.txt
index 302062653b..37e66c5ac9 100644
--- a/fastlane/metadata/android/pt-BR/full_description.txt
+++ b/fastlane/metadata/android/pt-BR/full_description.txt
@@ -1,3 +1,28 @@
-Trate sua vida como um jogo para se manter motivado e organizado! Habitica facilita a diversão enquanto cumpre metas.
-Insira seus Hábitos, suas Tarefas Diárias, e sua lista de afazeres e então crie um personagem customizado. Registre suas tarefas para subir de nível e desbloqueie recursos como armadura, animais de estimação, habilidades e até mesmo missões! Combata monstros com amigos para manter todos eles responsáveis, e use seu ouro em recompensas dentro do jogo, como equipamento, ou prêmios customizados, como assistir um episódio de seu show de TV favorito. Flexível, social, e divertido, Habitica é a maneira perfeita de se motivar para cumprir qualquer coisa.
-Se você tem qualquer pergunta, sinta-se livre para enviar seus comentários para mobile@habitica.com! E se você gostou de nosso aplicativo, nós iríamos gostar muito se você deixasse sua opinião.
\ No newline at end of file
+Trate sua vida como um jogo para se manter motivado e organizado! Habitica facilita a diversão ao cumprir suas metas. Insira seus Hábitos, seus objetivos Diários, sua lista de Afazeres e então crie um personagem personalizado.
+
+Marque como feitas suas tarefas para subir de nível e desbloqueie recursos como armaduras, mascotes, habilidades e até mesmo missões! Combata monstros com amigos para manter todos na linha enquanto usa seu ouro em recompensas dentro do jogo, como equipamento, ou prêmios personalizados como assistir a um episódio de sua série preferida. Flexível, social, e divertido, o Habitica é a maneira perfeita de se motivar para cumprir qualquer coisa.
+
+Características:
+ • Repetir automaticamente tarefas agendadas para suas rotinas diárias, semanais ou mensais
+ • Rastreador de hábitos flexível para tarefas que você deseja realizar várias vezes ao dia ou apenas de vez em quando
+ • Lista de tarefas tradicional para tarefas que só precisam ser realizadas uma vez
+ • Tarefas codificadas por cores e contadores de sequências ajudam você a ver rapidamente como está seu desempenho
+ • Sistema de nivelamento para visualizar seu progresso geral
+ • Toneladas de equipamentos colecionáveis e animais de estimação para combinar com seu estilo pessoal
+ • Personalizações inclusivas de avatar: cadeiras de rodas, estilos de cabelo, tons de pele e muito mais
+ • Lançamentos regulares de conteúdo e eventos sazonais para manter as coisas atualizadas
+ • Os grupos permitem que você se junte a amigos para maior responsabilidade e enfrente inimigos ferozes completando tarefas
+ • Os desafios oferecem listas de tarefas compartilhadas que você pode adicionar às suas tarefas pessoais
+ • Lembretes e widgets para ajudar você a se manter atualizado
+ • Temas de cores personalizáveis com modo claro e escuro
+ • Sincronização entre dispositivos
+
+Quer ainda mais flexibilidade para realizar suas tarefas em qualquer lugar? Temos um aplicativo Wear OS no relógio!
+
+Recursos do SO Wear:
+ • Visualizar, criar e concluir hábitos, tarefas diárias e tarefas
+ • Receba recompensas por seus esforços com experiência, comida, ovos e poções
+ • Acompanhe suas estatísticas com barras de progresso dinâmicas
+ • Mostre seu impressionante avatar de pixel no mostrador do relógio
+
+Se você tem qualquer pergunta, sinta-se à vontade para enviar seus comentários para mobile@habitica.com! E se você gostou de nosso aplicativo, nós iríamos adorar se você deixasse sua opinião.
\ No newline at end of file
diff --git a/fastlane/metadata/android/pt-PT/full_description.txt b/fastlane/metadata/android/pt-PT/full_description.txt
new file mode 100644
index 0000000000..7b6fa12c18
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/full_description.txt
@@ -0,0 +1,28 @@
+Trate a sua vida como um jogo para se manter motivado e organizado! Habitica torna simples para se divertir ao realizar metas. Introduza os seus Hábitos, os seus objetivos diários e a sua lista de tarefas, e crie um avatar personalizado.
+
+Confira tarefas para subir de nível de seu avatar e desbloquear recursos como armaduras, animais de estimação, habilidades e até mesmo missões! Lute contra monstros com amigos para se manterem responsáveis e use seu ouro em recompensas no jogo, como equipamentos ou prêmios personalizados, como assistir a um episódio de seu programa de TV favorito. Flexível, social e divertido, o Habitica é a maneira perfeita de se motivar para realizar qualquer coisa.
+
+Características:
+ • Repetir automaticamente tarefas agendadas para suas rotinas diárias, semanais ou mensais
+ • Rastreador de hábitos flexível para tarefas que você deseja realizar várias vezes ao dia ou apenas de vez em quando
+ • Lista de tarefas tradicional para tarefas que só precisam ser realizadas uma vez
+ • Tarefas codificadas por cores e contadores de sequências ajudam você a ver rapidamente como está seu desempenho
+ • Sistema de nivelamento para visualizar seu progresso geral
+ • Toneladas de equipamentos colecionáveis e animais de estimação para combinar com seu estilo pessoal
+ • Personalizações inclusivas de avatar: cadeiras de rodas, estilos de cabelo, tons de pele e muito mais
+ • Lançamentos regulares de conteúdo e eventos sazonais para manter as coisas atualizadas
+ • Os grupos permitem que você se junte a amigos para maior responsabilidade e enfrente inimigos ferozes completando tarefas
+ • Os desafios oferecem listas de tarefas compartilhadas que você pode adicionar às suas tarefas pessoais
+ • Lembretes e widgets para ajudar você a se manter atualizado
+ • Temas de cores personalizáveis com modo claro e escuro
+ • Sincronização entre dispositivos
+
+Quer ainda mais flexibilidade para realizar suas tarefas em qualquer lugar? Temos um aplicativo Wear OS no relógio!
+
+Recursos do SO Wear:
+ • Visualizar, criar e concluir hábitos, tarefas diárias e tarefas
+ • Receba recompensas por seus esforços com experiência, comida, ovos e poções
+ • Acompanhe suas estatísticas com barras de progresso dinâmicas
+ • Mostre seu impressionante avatar de pixel no mostrador do relógio
+
+Se você tiver alguma dúvida, sinta-se à vontade para enviar feedback para mobile@habitica.com! E se você gosta de nosso aplicativo, nós realmente apreciaríamos se você nos deixasse um comentário.
\ No newline at end of file
diff --git a/fastlane/metadata/android/pt-PT/short_description.txt b/fastlane/metadata/android/pt-PT/short_description.txt
new file mode 100644
index 0000000000..7cf3f13758
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/short_description.txt
@@ -0,0 +1 @@
+Trate a sua vida como um jogo para manter-se motivado e organizado!
\ No newline at end of file
diff --git a/fastlane/metadata/android/pt-PT/title.txt b/fastlane/metadata/android/pt-PT/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/pt-PT/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/pt-PT/video.txt b/fastlane/metadata/android/pt-PT/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/ru-RU/full_description.txt b/fastlane/metadata/android/ru-RU/full_description.txt
index c681cc2323..eb264ec3ff 100644
--- a/fastlane/metadata/android/ru-RU/full_description.txt
+++ b/fastlane/metadata/android/ru-RU/full_description.txt
@@ -1,3 +1,28 @@
-Относитесь к своей жизни, как к игре, чтобы оставаться мотивированным и организованным! Habitica позволяет легко развлекаться при достижении целей.
-Введите свои привычки, свои ежедневные цели и список дел, а затем создайте собственный аватар. Выполняйте задачи, чтобы повысить уровень вашего аватара и разблокировать такие функции, как снаряжение, питомцы, навыки и даже квесты! Сражайтесь против монстрами с друзьями, чтобы поддержать друг друга , и использовать свое золото в игре в качестве поощрения, например, на экипировку или специальные награды, или даже на просмотр эпизода вашего любимого телешоу. Гибкая, социальная и веселая, Habitica - это идеальный способ мотивировать себя к чему-либо ».
-Если у вас есть какие-либо вопросы, не стесняйтесь отправлять их на mobile@habitica.com! А если вам понравится наше приложение, мы будем очень признательны, если вы оставите нам отзыв ».
\ No newline at end of file
+Относитесь к своей жизни, как к игре, чтобы оставаться мотивированным и организованным! Habitica позволяет легко развлекаться при достижении целей. Введите свои привычки, ежедневные цели и список дел, а затем создайте собственный аватар.
+
+Выполняйте задачи, чтобы повысить уровень вашего аватара и разблокировать такие функции, как снаряжение, питомцы, навыки и даже квесты! Сражайтесь против монстраов с друзьями, чтобы поддерживать друг друга, и используйте свое золото в игре в качестве поощрения, например, на экипировку или специальные награды, или даже на просмотр эпизода вашего любимого телешоу. Гибкая, социальная и веселая, Habitica - это идеальный способ мотивировать себя к чему-либо. Если у вас есть какие-либо вопросы, не стесняйтесь отправлять их на mobile@habitica.com! А если вам понравится наше приложение, мы будем очень признательны, если вы оставите нам отзыв.
+
+Функции:
+ • Автоматическое повторение задач, запланированных для ваших ежедневных, еженедельных или ежемесячных задач.
+ • Гибкий трекер привычек для задач, которые вы хотите выполнять несколько раз в день или время от времени.
+ • Традиционный список дел, которые нужно выполнить только один раз.
+ • Задания с цветовой кодировкой и счетчики серий помогают сразу увидеть, как у вас идут дела.
+ • Система уровней для визуализации вашего общего прогресса.
+ • Множество коллекционного снаряжения и домашних животных, которые подойдут вашему личному стилю.
+ • Инклюзивная настройка аватара: инвалидные коляски, прически, оттенки кожи и т. д.
+ • Регулярные выпуски контента и сезонные мероприятия для поддержания свежести контента.
+ • Группы позволяют вам объединяться с друзьями, чтобы повысить ответственность и сражаться с жестокими врагами, выполняя задания.
+ • В задачах есть общие списки задач, которые можно добавить к личным задачам.
+ • Напоминания и виджеты, которые помогут вам не сбиться с пути.
+ • Настраиваемые цветовые темы с темным и светлым режимами.
+ • Синхронизация между устройствами
+
+Хотите еще большей гибкости, чтобы решать свои задачи на ходу? На часах установлено приложение Wear OS!
+
+Возможности Wear OS:
+ • Просматривайте, создавайте и заполняйте привычки, ежедневные задания и задачи.
+ • Получайте награды за свои усилия в виде опыта, еды, яиц и зелий.
+ • Отслеживайте свою статистику с помощью динамических индикаторов выполнения.
+ • Покажите свой потрясающий пиксельный аватар на циферблате.
+
+Если у вас есть какие-либо вопросы, не стесняйтесь отправлять их на mobile@habitica.com! А если вам понравится наше приложение, мы будем очень признательны, если вы оставите нам отзыв.
\ No newline at end of file
diff --git a/fastlane/metadata/android/sv-SE/full_description.txt b/fastlane/metadata/android/sv-SE/full_description.txt
new file mode 100644
index 0000000000..c97b4ba98e
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/full_description.txt
@@ -0,0 +1,28 @@
+Behandla ditt liv som ett spel för att förbli motiverad och organiserad! Habitica gör det enkelt att ha kul medan man uppnår mål. Mata in dina Vanor, dina Dagliga mål och din Att Göra lista, sedan skapa en anpassad karaktär.
+
+Bocka av uppgifter för att levela upp din karaktär och låsa upp saker som rustning, sällskapsdjur, förmågor och till och med uppdrag! Slåss mot monster med vänner för att hålla varandra ansvariga, och använd ditt guld på belöningar i spelet, som utrustning, eller anpassade priser, som att kolla på ett avsnitt av din favorit TV-serie. Flexibelt, socialt, kul, Habitica är det perfekta sättet att motivera dig själv till att uppnå vad som helst.
+
+Funktioner:
+ • Upprepa automatiskt uppgifter schemalagda för dina dagliga, veckovisa eller månatliga rutiner
+ • Flexibel vanespårare för uppgifter du vill göra flera gånger om dagen eller bara en gång i taget
+ • Traditionell att göra-lista för uppgifter som bara behöver göras en gång
+ • Färgkodade uppgifter och streckräknare hjälper dig att se hur du gör med ett ögonkast
+ • Utjämningssystem för att visualisera dina övergripande framsteg
+ • Massor av samlarutrustning och husdjur som passar din personliga stil
+ • Inkluderande avataranpassningar: rullstolar, frisyrer, hudtoner och mer
+ • Regelbundna innehållssläpp och säsongsbetonade evenemang för att hålla saker fräscha
+ • Fester låter dig slå dig ihop med vänner för extra ansvar och slåss mot hårda fiender genom att slutföra uppgifter
+ • Utmaningar erbjuder delade uppgiftslistor som du kan lägga till dina personliga uppgifter
+ • Påminnelser och widgets som hjälper dig att hålla dig på rätt spår
+ • Anpassningsbara färgteman med mörkt och ljust läge
+ • Synkronisera mellan enheter
+
+Vill du ha ännu mer flexibilitet för att ta dina uppgifter på språng? Vi har en Wear OS-app på klockan!
+
+Wear OS-funktioner:
+ • Visa, skapa och komplettera vanor, dagstidningar och att göra
+ • Få belöningar för dina ansträngningar med erfarenhet, mat, ägg och drycker
+ • Spåra din statistik med dynamiska förloppsindikatorer
+ • Visa upp din fantastiska pixelavatar på urtavlan
+
+Om du har några frågor, varsågod och skicka feedback till mobile@habitica.com! Och om du gillar vår app, skulle vi uppskatta det om du skulle lämna en recension.
\ No newline at end of file
diff --git a/fastlane/metadata/android/sv-SE/short_description.txt b/fastlane/metadata/android/sv-SE/short_description.txt
new file mode 100644
index 0000000000..d7633f90c8
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/short_description.txt
@@ -0,0 +1 @@
+Behandla ditt liv som ett spel för att förbli motiverad och organiserad!
\ No newline at end of file
diff --git a/fastlane/metadata/android/sv-SE/title.txt b/fastlane/metadata/android/sv-SE/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/sv-SE/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/sv-SE/video.txt b/fastlane/metadata/android/sv-SE/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/tr-TR/full_description.txt b/fastlane/metadata/android/tr-TR/full_description.txt
index c18bd92f08..d0ea20ea4a 100644
--- a/fastlane/metadata/android/tr-TR/full_description.txt
+++ b/fastlane/metadata/android/tr-TR/full_description.txt
@@ -1,5 +1,28 @@
-Hayatınızı motive etmek ve organize olmak için bir oyun gibi davranın! Habitica, amaçları gerçekleştirirken eğlenmeyi kolaylaştırır.
+Hayatınızı motive etmek ve organize olmak için bir oyun gibi davranın! Habitica, amaçları gerçekleştirirken eğlenmeyi kolaylaştırır. Alışkanlıklarınızı, Günlük hedeflerinizi ve Yapılacaklar listenizi girin ve özel bir avatar oluşturun.
-Alışkanlıklarınızı, Günlük hedeflerinizi ve Yapılacaklar listenizi girin ve özel bir avatar oluşturun. Avatarınızı özelleştirmek için zırh, evcil hayvan, beceri ve hatta görevler gibi özellikleri açmak için görevleri kontrol edin! Canavarları birbirleriyle hesaplı tutmak için arkadaşlarınızla savaşın ve altınınızı teçhizat gibi oyun içi ödüller veya en sevdiğiniz TV şovunun bir bölümünü izlemek gibi özel ödüller için kullanın. Esnek, sosyal ve eğlenceli olan Habitica, kendinizi herhangi bir şeyi başarmak için motive etmenin mükemmel bir yoldur.
+Avatarınızı özelleştirmek için zırh, evcil hayvan, beceri ve hatta görevler gibi özellikleri açmak için görevleri kontrol edin! Canavarları birbirleriyle hesaplı tutmak için arkadaşlarınızla savaşın ve altınınızı teçhizat gibi oyun içi ödüller veya en sevdiğiniz TV şovunun bir bölümünü izlemek gibi özel ödüller için kullanın. Esnek, sosyal ve eğlenceli olan Habitica, kendinizi herhangi bir şeyi başarmak için motive etmenin mükemmel bir yoldur.
+
+Özellikler:
+ • Günlük, haftalık veya aylık rutinleriniz için planlanan görevlerin otomatik olarak tekrarlanması
+ • Günde birden çok kez veya yalnızca arada bir yapmak istediğiniz görevler için esnek alışkanlık izleyici
+ • Yalnızca bir kez yapılması gereken görevler için geleneksel yapılacaklar listesi
+ • Renk kodlu görevler ve seri sayaçları, bir bakışta ne durumda olduğunuzu görmenize yardımcı olur
+ • Genel ilerlemenizi görselleştirmek için seviyelendirme sistemi
+ • Kişisel tarzınıza uygun tonlarca koleksiyon eşyası ve evcil hayvan
+ • Kapsamlı avatar özelleştirmeleri: tekerlekli sandalyeler, saç stilleri, ten tonları ve daha fazlası
+ • Her şeyi taze tutmak için düzenli içerik yayınları ve sezonluk etkinlikler
+ • Partiler, ekstra sorumluluk için arkadaşlarınızla ekip kurmanıza ve görevleri tamamlayarak şiddetli düşmanlarla savaşmanıza olanak tanır
+ • Mücadeleler, kişisel görevlerinize ekleyebileceğiniz paylaşılan görev listeleri sunar
+ • Doğru yolda kalmanıza yardımcı olacak hatırlatıcılar ve widget'lar
+ • Koyu ve açık modlu özelleştirilebilir renk temaları
+ • Cihazlar arasında senkronizasyon
+
+Hareket halindeyken görevlerinizi yerine getirmek için daha fazla esneklik mi istiyorsunuz? Saatimizde bir Wear OS uygulamamız var!
+
+İşletim sistemi özelliklerini kullanın:
+ • Alışkanlıkları, Günlük İşleri ve Yapılacaklar'ı görüntüleyin, oluşturun ve tamamlayın
+ • Deneyim, yiyecek, yumurta ve iksirlerle ilgili çabalarınızın karşılığını alın
+ • Dinamik ilerleme çubuklarıyla istatistiklerinizi takip edin
+ • Çarpıcı piksel avatarınızı saat kadranında sergileyin
Herhangi bir sorunuz varsa, mobile@habitica.com adresine geri bildirim göndermekten çekinmeyin! Ve uygulamanızdan hoşlanıyorsanız, bize bir inceleme bırakırsanız memnun oluruz.
\ No newline at end of file
diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt
new file mode 100644
index 0000000000..75a74ae797
--- /dev/null
+++ b/fastlane/metadata/android/uk/full_description.txt
@@ -0,0 +1,28 @@
+Ставтеся до свого життя як до гри, щоб залишатися мотивованими та організованими! Habitica дозволяє легко розважатися під час досягнення цілей. Введіть свої звички, щоденні цілі та список справ, а потім створіть власний аватар.
+
+Відмітьте завдання, щоб підвищити рівень свого аватара та розблокувати такі функції, як броня, домашні тварини, навички та навіть квести! Боріться з монстрами разом з друзями, щоб тримати один одного підзвітними, і використовуйте своє золото на ігрові нагороди, як-от спорядження або спеціальні нагороди, як-от перегляд епізоду улюбленого телешоу. Гнучка, соціальна та весела, Habitica — ідеальний спосіб мотивувати себе на будь-які досягнення.
+
+особливості:
+ • Автоматичне повторення завдань, запланованих для щоденних, щотижневих або щомісячних процедур
+ • Гнучкий засіб відстеження звичок для завдань, які ви хочете виконувати кілька разів на день або лише час від часу
+ • Традиційний список завдань, які потрібно виконати лише один раз
+ • Завдання, позначені кольором, і лічильники поспіль допоможуть вам одразу побачити, як ви справляєтеся
+ • Система вирівнювання для візуалізації вашого загального прогресу
+ • Безліч колекційного спорядження та домашніх тварин відповідно до вашого стилю
+ • Включні налаштування аватара: інвалідні візки, зачіски, відтінки шкіри тощо
+ • Регулярні випуски вмісту та сезонні події, щоб підтримувати актуальність
+ • Партії дозволяють вам об’єднатися з друзями для додаткової відповідальності та битви з лютими ворогами, виконуючи завдання
+ • Завдання пропонують спільні списки завдань, які ви можете додати до своїх особистих завдань
+ • Нагадування та віджети, які допоможуть вам бути в курсі
+ • Настроювані кольорові теми з темним і світлим режимами
+ • Синхронізація між пристроями
+
+Хочете ще більше гнучкості, щоб виконувати свої завдання на ходу? У нас є додаток Wear OS на годиннику!
+
+Функції Wear OS:
+ • Переглядайте, створюйте та виконуйте звички, щоденні справи та справи
+ • Отримуйте нагороду за свої зусилля досвідом, їжею, яйцями та зіллям
+ • Відстежуйте свою статистику за допомогою динамічних індикаторів прогресу
+ • Покажіть свій приголомшливий піксельний аватар на циферблаті
+
+Якщо у Вас виникли запитання, не соромтеся надсилати відгук на адресу mobile@habitica.com! І якщо Вам подобається наш додаток, ми будемо дуже вдячні, якщо Ви залишите нам відгук.
\ No newline at end of file
diff --git a/fastlane/metadata/android/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt
new file mode 100644
index 0000000000..1cc9a1a0d4
--- /dev/null
+++ b/fastlane/metadata/android/uk/short_description.txt
@@ -0,0 +1 @@
+Ставтеся до свого життя як до гри, щоб залишатися мотивованими та організованими
\ No newline at end of file
diff --git a/fastlane/metadata/android/uk/title.txt b/fastlane/metadata/android/uk/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/uk/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/uk/video.txt b/fastlane/metadata/android/uk/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt
new file mode 100644
index 0000000000..2ff82aa196
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/full_description.txt
@@ -0,0 +1,28 @@
+让您的生活像游戏一样有趣。保持您对生活的积极性与条理性!Habitica帮助您轻松享受达成目标的乐趣。 输入您的习惯、每日目标与任务清单,然后创建出自己的个性化身。
+
+通过完成任务来升级自己,同时解锁盔甲、宠物、技能,甚至副本卷轴!您还可以和朋友们并肩作战,打败怪物,增强彼此的责任心!作为奖励,您可以使用金币购买游戏内奖励商品,比如装备;或者亲自设计一个奖励,比如看一集最喜欢的电视节目。Habitica兼具灵活性,社交性,趣味性,您会在它的激励下完成所有您想做的事情。
+
+特征:
+ • 自动重复为您的每日、每周或每月例程安排的任务
+ • 灵活的习惯跟踪器,适合您每天想要执行多次或偶尔执行一次的任务
+ • 传统的待办事项列表只需要完成一次的任务
+ • 颜色编码的任务和连胜计数器可帮助您一目了然地了解自己的表现
+ • 分级系统可直观地显示您的整体进度
+ • 大量可收藏的装备和宠物,适合您的个人风格
+ • 包容性头像定制:轮椅、发型、肤色等
+ • 定期内容发布和季节性活动以保持新鲜感
+ • 派对让您与朋友组队承担额外责任,并通过完成任务与凶猛的敌人战斗
+ • 挑战提供共享任务列表,您可以将其添加到您的个人任务中
+ • 提醒和小部件可帮助您保持正轨
+ • 可定制的颜色主题,包括深色和浅色模式
+ • 跨设备同步
+
+想要更加灵活地随时随地处理任务吗?我们在手表上安装了 Wear OS 应用程序!
+
+Wear 操作系统特点:
+ • 查看、创建和完成习惯、每日任务和待办事项
+ • 通过经验、食物、鸡蛋和药水获得你的努力奖励
+ • 使用动态进度条跟踪您的统计数据
+ • 在表盘上展示您令人惊叹的像素头像
+
+如果您有任何疑问,请尽管反馈至mobile@habitica.com !如果您喜欢我们的应用,我们同样真诚地希望您留下您宝贵的评论。
\ No newline at end of file
diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt
new file mode 100644
index 0000000000..ad957ff866
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/short_description.txt
@@ -0,0 +1 @@
+让您的生活就像游戏一样有趣。保持您对生活的积极性与条理性!
\ No newline at end of file
diff --git a/fastlane/metadata/android/zh-CN/title.txt b/fastlane/metadata/android/zh-CN/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/zh-CN/video.txt b/fastlane/metadata/android/zh-CN/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/fastlane/metadata/android/zh-TW/full_description.txt b/fastlane/metadata/android/zh-TW/full_description.txt
new file mode 100644
index 0000000000..6f9e294b52
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/full_description.txt
@@ -0,0 +1,29 @@
+生活即是遊戲,保持動力並讓生活井井有條!Habitica 簡單地讓完成日常目標的同時,又能得到樂趣。輸入你的習慣、日常目標、待辦事項,然後創建一個角色。
+
+完成任務去升級你的角色和解鎖遊戲系統,例如護甲、寵物、技能、甚至副本任務!和朋友一起相互監督去對抗怪物,使用你的金幣兌換遊戲內獎勵,例如裝備;或是兌換自訂獎勵,例如追劇。機制設定靈活、兼具社交功能、又實在是很好玩,Habitica 是個激勵自己去完成任何事情的完美工具。
+
+
+特徵:
+ • 自動重複為您的每日、每週或每月例程安排的任務
+ • 靈活的習慣跟踪器,適合您每天想要執行多次或偶爾執行一次的任務
+ • 傳統的待辦事項列表只需要完成一次的任務
+ • 顏色編碼的任務和連勝計數器可幫助您一目了然地了解自己的表現
+ • 分級系統可直觀地顯示您的整體進度
+ • 大量可收藏的裝備和寵物,適合您的個人風格
+ • 包容性頭像定制:輪椅、髮型、膚色等
+ • 定期內容髮布和季節性活動以保持新鮮感
+ • 派對讓您與朋友組隊承擔額外責任,並通過完成任務與兇猛的敵人戰鬥
+ • 挑戰提供共享任務列表,您可以將其添加到您的個人任務中
+ • 提醒和小部件可幫助您保持正軌
+ • 可定制的顏色主題,包括深色和淺色模式
+ • 跨設備同步
+
+想要更加靈活地隨時隨地處理任務嗎?我們在手錶上安裝了 Wear OS 應用程序!
+
+Wear 操作系統特點:
+ • 查看、創建和完成習慣、每日任務和待辦事項
+ • 通過經驗、食物、雞蛋和藥水獲得你的努力獎勵
+ • 使用動態進度條跟踪您的統計數據
+ • 在錶盤上展示您令人驚嘆的像素頭像
+
+如果你有任何問題,請發送意見到 mobile@habitica.com!並且如果你喜歡我們的 app,而能給我們留下一條意見的話,我們會相當感謝。
\ No newline at end of file
diff --git a/fastlane/metadata/android/zh-TW/short_description.txt b/fastlane/metadata/android/zh-TW/short_description.txt
new file mode 100644
index 0000000000..f712542fec
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/short_description.txt
@@ -0,0 +1 @@
+生活即是遊戲,保持動力並讓生活井井有條!
\ No newline at end of file
diff --git a/fastlane/metadata/android/zh-TW/title.txt b/fastlane/metadata/android/zh-TW/title.txt
new file mode 100644
index 0000000000..a4b770e432
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/title.txt
@@ -0,0 +1 @@
+Habitica
\ No newline at end of file
diff --git a/fastlane/metadata/android/zh-TW/video.txt b/fastlane/metadata/android/zh-TW/video.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2decf50b69..54b29ecf48 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Sun May 08 23:57:52 EDT 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
index 24fb3f996e..2483914233 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,5 @@
plugins {
- id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0'
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0'
}
include 'Habitica', ':Habitica'
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 7c0bce154a..06b3a4fc1c 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -3,7 +3,7 @@ plugins {
id("com.android.library")
id("kotlin-parcelize")
id("kotlin-kapt")
- id("io.kotest.multiplatform") version "5.5.5"
+ id("io.kotest.multiplatform") version "5.6.2"
}
allprojects {
diff --git a/version.properties b/version.properties
index e2b30a0c08..f08f214553 100644
--- a/version.properties
+++ b/version.properties
@@ -1,2 +1,2 @@
-NAME=4.2.2
-CODE=6121
\ No newline at end of file
+NAME=4.3
+CODE=6681
\ No newline at end of file
diff --git a/wearos/build.gradle b/wearos/build.gradle
index 0d3291b970..fcc9311499 100644
--- a/wearos/build.gradle
+++ b/wearos/build.gradle
@@ -22,8 +22,9 @@ android {
applicationId "com.habitrpg.android.habitica"
minSdk 26
targetSdk target_sdk
+ compileSdk 34
versionCode app_version_code + 1
- versionName app_version_name
+ versionName "${app_version_name}w"
buildConfigField "String", "TESTING_LEVEL", "\"production\""
def formattedDate = new Date().format('yyMMdd')
@@ -47,7 +48,6 @@ android {
debug {
minifyEnabled false
debuggable true
- applicationIdSuffix ".debug"
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
resValue "string", "app_name", "Habitica Debug"
@@ -113,7 +113,7 @@ dependencies {
implementation "androidx.core:core-ktx:$core_ktx_version"
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
- implementation 'androidx.wear:wear:1.2.0'
+ implementation 'androidx.wear:wear:1.3.0'
implementation "androidx.wear:wear-input:1.1.0"
//Networking
@@ -141,7 +141,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "androidx.preference:preference-ktx:$preferences_version"
- implementation "androidx.navigation:navigation-fragment-ktx:2.5.3"
+ implementation "androidx.navigation:navigation-fragment-ktx:2.7.3"
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
@@ -154,6 +154,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
+ implementation "androidx.core:core-splashscreen:1.1.0-alpha02"
+
testImplementation "io.mockk:mockk:$mockk_version"
testImplementation "io.mockk:mockk-android:$mockk_version"
testImplementation "io.kotest:kotest-runner-junit5:$kotest_version"
diff --git a/wearos/src/main/AndroidManifest.xml b/wearos/src/main/AndroidManifest.xml
index 69db7d18de..3601f87e18 100644
--- a/wearos/src/main/AndroidManifest.xml
+++ b/wearos/src/main/AndroidManifest.xml
@@ -7,12 +7,12 @@
+ android:theme="@style/Theme.App.Starting">
@@ -23,35 +23,82 @@
-
-
+ android:taskAffinity=""
+ android:theme="@style/HabiticaAppTheme" />
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
\ No newline at end of file
+
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/BaseActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/BaseActivity.kt
index 62eb48dd12..e18ecc85b4 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/BaseActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/BaseActivity.kt
@@ -113,7 +113,7 @@ abstract class BaseActivity : ComponentActi
val info = Tasks.await(
capabilityClient.getCapability(
permission,
- CapabilityClient.FILTER_REACHABLE
+ CapabilityClient.FILTER_ALL
)
)
val nodeID = info.nodes.firstOrNull()
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt
index 550340237b..86cbdb5520 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/LoginActivity.kt
@@ -1,10 +1,8 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.app.Activity
-import android.app.AlertDialog
import android.content.Intent
import android.os.Bundle
-import android.text.method.PasswordTransformationMethod
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.core.view.isVisible
@@ -20,8 +18,7 @@ import dagger.hilt.android.AndroidEntryPoint
class LoginActivity : BaseActivity() {
enum class State {
INITIAL,
- OTHER,
- INPUT
+ OTHER
}
override val viewModel: LoginViewModel by viewModels()
private var currentState: State = State.INITIAL
@@ -34,35 +31,17 @@ class LoginActivity : BaseActivity() {
binding.otherButton.isVisible = true
binding.googleLoginButton.isVisible = false
binding.registerButton.isVisible = false
- binding.usernamePasswordButton.isVisible = false
- binding.usernameEditText.isVisible = false
- binding.passwordEditText.isVisible = false
- binding.loginButton.isVisible = false
}
State.OTHER -> {
binding.descriptionView.isVisible = false
binding.signInOnPhoneButton.isVisible = false
binding.otherButton.isVisible = false
binding.googleLoginButton.isVisible = true
- binding.registerButton.isVisible = binding.registerButton.isEnabled
- binding.usernamePasswordButton.isVisible = true
- binding.usernameEditText.isVisible = false
- binding.passwordEditText.isVisible = false
- binding.loginButton.isVisible = false
- }
- State.INPUT -> {
- binding.descriptionView.isVisible = false
- binding.signInOnPhoneButton.isVisible = false
- binding.otherButton.isVisible = false
- binding.googleLoginButton.isVisible = false
- binding.registerButton.isVisible = false
- binding.usernamePasswordButton.isVisible = false
- binding.usernameEditText.isVisible = true
- binding.passwordEditText.isVisible = true
- binding.loginButton.isVisible = true
+ binding.registerButton.isVisible = true
+ binding.registerButton.alpha = if (binding.registerButton.isEnabled) 1.0f else 0.7f
}
}
- binding.root.smoothScrollTo(0, 0)
+ binding.scrollView.smoothScrollTo(0, 0)
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -75,20 +54,10 @@ class LoginActivity : BaseActivity() {
binding.signInOnPhoneButton.setOnClickListener { openLoginOnPhone() }
binding.otherButton.setOnClickListener { currentState = State.OTHER }
- binding.usernamePasswordButton.setOnClickListener { currentState = State.INPUT }
- binding.loginButton.setOnClickListener { loginLocal() }
binding.googleLoginButton.setOnClickListener { loginGoogle() }
binding.registerButton.setOnClickListener { openRegisterOnPhone() }
- binding.passwordEditText.transformationMethod = PasswordTransformationMethod()
- binding.usernameEditText.doOnTextChanged { text, start, before, count ->
- setLoginButtonIsEnabled()
- }
- binding.passwordEditText.doOnTextChanged { text, start, before, count ->
- setLoginButtonIsEnabled()
- }
-
currentState = State.INITIAL
}
@@ -100,37 +69,10 @@ class LoginActivity : BaseActivity() {
openRemoteActivity(DeviceCommunication.SHOW_LOGIN)
}
- private fun loginLocal() {
- val username: String = binding.usernameEditText.text.toString().trim { it <= ' ' }
- val password: String = binding.passwordEditText.text.toString()
- if (username.isEmpty() || password.isEmpty()) {
- showValidationError(getString(R.string.login_validation_error_fieldsmissing))
- return
- }
- viewModel.login(username, password) {
- stopAnimatingProgress()
- }
- startAnimatingProgress()
- }
-
private fun loginGoogle() {
viewModel.handleGoogleLogin(this, pickAccountResult)
}
- private fun showValidationError(message: String) {
- val alert = AlertDialog.Builder(this).create()
- alert.setTitle(R.string.login_validation_error_title)
- alert.setMessage(message)
- alert.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.ok)) { thisAlert, _ ->
- thisAlert.dismiss()
- }
- alert.show()
- }
-
- private fun setLoginButtonIsEnabled() {
- binding.loginButton.isEnabled = binding.usernameEditText.text.isNotEmpty() && binding.passwordEditText.text.isNotEmpty()
- }
-
private val pickAccountResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
viewModel.handleGoogleLoginResult(this, task, recoverFromPlayServicesErrorResult)
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt
index 2fdb480e87..5653194e56 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/MainActivity.kt
@@ -32,7 +32,7 @@ class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
- binding.root.apply {
+ binding.recyclerView.apply {
layoutManager =
WearableLinearLayoutManager(this@MainActivity, HabiticaScrollingLayoutCallback())
adapter = this@MainActivity.adapter
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt
index 915c32c8e3..b67443d2f5 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/RYAActivity.kt
@@ -30,13 +30,13 @@ class RYAActivity : BaseActivity() {
viewModel.tasks.observe(
this,
object : Observer> {
- override fun onChanged(list: List) {
- if (list.isEmpty()) {
+ override fun onChanged(value: List) {
+ if (value.isEmpty()) {
runCron()
viewModel.tasks.removeObserver(this)
} else {
binding.scrollView.isVisible = true
- createTaskListViews(list)
+ createTaskListViews(value)
viewModel.tasks.removeObserver(this)
}
}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/SettingsActivity.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/SettingsActivity.kt
index 5ff13ce66d..20a6df1d13 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/SettingsActivity.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/activities/SettingsActivity.kt
@@ -27,7 +27,7 @@ class SettingsActivity : BaseActivity() {
override val viewModel: SplashViewModel by viewModels()
- override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
- super.onCreate(savedInstanceState, persistentState)
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
+ installSplashScreen()
binding = ActivitySplashBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState)
if (viewModel.hasAuthentication) {
@@ -81,7 +78,6 @@ class SplashActivity : BaseActivity() {
} else {
stopAnimatingProgress()
}
- binding.textView.isVisible = show
delay(90.toDuration(DurationUnit.SECONDS))
if (isActive) {
// the sync attempt has timed out
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt
index 488fbe2d4e..86a50db88d 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/viewmodels/LoginViewModel.kt
@@ -154,16 +154,6 @@ class LoginViewModel @Inject constructor(
}
}
- fun login(username: String, password: String, onResult: (Boolean) -> Unit) {
- viewModelScope.launch(exceptionBuilder.userFacing(this)) {
- val response = apiClient.loginLocal(UserAuth(username, password)).responseData
- handleAuthResponse(response)
- onResult(response?.id != null)
- }.invokeOnCompletion {
- onResult(it == null)
- }
- }
-
companion object {
private const val REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR = 1001
}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/AddTaskButton.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/AddTaskButton.kt
index 7c63f8adba..9b7114056c 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/AddTaskButton.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/AddTaskButton.kt
@@ -61,10 +61,8 @@ class AddTaskButton @JvmOverloads constructor(
invalidate()
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
-
- if (canvas == null) return
path.reset()
path.addArc(rect, 180f, 360f)
canvas.drawPath(path, fillPaint)
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt
index 4f79f3f646..df3df18725 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/CircularProgressView.kt
@@ -51,9 +51,9 @@ class CircularProgressView(
strokeCap = Paint.Cap.ROUND
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
setSpace()
- canvas?.let {
+ canvas.let {
drawBackgroundArc(it)
drawInnerArc(it)
}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/ConnectedActionChipView.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/ConnectedActionChipView.kt
index 1162848a7a..28413dd755 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/ConnectedActionChipView.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/ConnectedActionChipView.kt
@@ -20,11 +20,12 @@ class ConnectedActionChipView(context: Context, attrs: AttributeSet? = null) :
private fun checkIfPhoneAvailable() {
MainScope().launch(Dispatchers.IO) {
- val result = Tasks.await(capabilityClient.getCapability("open_activity", CapabilityClient.FILTER_REACHABLE))
- launch(Dispatchers.Main) {
- isEnabled = result.nodes.firstOrNull { it.isNearby } != null
- isVisible = isEnabled
- }
+ capabilityClient.addListener( {
+ launch(Dispatchers.Main) {
+ isEnabled = it.nodes.firstOrNull { it.isNearby } != null
+ alpha = if (isEnabled) 1.0f else 0.7f
+ }
+ }, "open_activity")
}
}
}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabitDirectionPickerButton.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabitDirectionPickerButton.kt
index 4f796b7b5d..387ecf4693 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabitDirectionPickerButton.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabitDirectionPickerButton.kt
@@ -68,10 +68,9 @@ class HabitDirectionPickerButton @JvmOverloads constructor(
invalidate()
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- if (canvas == null) return
path.reset()
if (drawFromTop) {
path.addArc(rect, 0f, 180f)
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaRecyclerView.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaRecyclerView.kt
index 63fcd037de..c8050c31aa 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaRecyclerView.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaRecyclerView.kt
@@ -13,6 +13,13 @@ class HabiticaRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : WearableRecyclerView(context, attrs) {
+
+ init {
+ isVerticalScrollBarEnabled = true
+ focusable = View.FOCUSABLE
+ isFocusableInTouchMode = true
+ }
+
override fun onAttachedToWindow() {
super.onAttachedToWindow()
post {
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaScrollView.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaScrollView.kt
index 57c53a1b11..a767a1b904 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaScrollView.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/HabiticaScrollView.kt
@@ -3,6 +3,7 @@ package com.habitrpg.wearos.habitica.ui.views
import android.content.Context
import android.content.res.Resources
import android.util.AttributeSet
+import android.view.View
import androidx.core.view.children
import androidx.core.widget.NestedScrollView
import com.habitrpg.common.habitica.extensions.dpToPx
@@ -12,6 +13,12 @@ class HabiticaScrollView @JvmOverloads constructor(
attrs: AttributeSet? = null
) : NestedScrollView(context, attrs) {
+ init {
+ isVerticalScrollBarEnabled = true
+ focusable = View.FOCUSABLE
+ isFocusableInTouchMode = true
+ }
+
override fun onAttachedToWindow() {
super.onAttachedToWindow()
requestFocus()
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/IndeterminateProgressView.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/IndeterminateProgressView.kt
index d8f8e16f64..444278237b 100644
--- a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/IndeterminateProgressView.kt
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/IndeterminateProgressView.kt
@@ -86,9 +86,8 @@ class IndeterminateProgressView @JvmOverloads constructor(
}
}
- override fun onDraw(canvas: Canvas?) {
+ override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- if (canvas == null) return
val halfBar = paint.strokeWidth / 2f
if (isCircular) {
canvas.drawArc(halfBar, halfBar, width.toFloat() - halfBar, height.toFloat() - halfBar, currentAngle, 360f, false, paint)
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/TextViewWrapper.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/TextViewWrapper.kt
new file mode 100644
index 0000000000..3be1f62e20
--- /dev/null
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/TextViewWrapper.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.wearable.timetext
+
+import android.view.View
+import android.widget.TextView
+import androidx.wear.widget.CurvedTextView
+
+/**
+ * A wrapper around a [TextView] like object, that may not actually extend [TextView] (like a [CurvedTextView]).
+ */
+interface TextViewWrapper {
+ val view: View
+ var text: CharSequence?
+ var textColor: Int
+}
+
+/**
+ * A [TextViewWrapper] wrapping a [CurvedTextView].
+ */
+class CurvedTextViewWrapper(
+ override val view: CurvedTextView
+) : TextViewWrapper {
+ override var text: CharSequence?
+ get() = view.text
+ set(value) {
+ view.text = value?.toString().orEmpty()
+ }
+
+ override var textColor: Int
+ get() = view.textColor
+ set(value) {
+ view.textColor = value
+ }
+}
+
+/**
+ * A [TextViewWrapper] wrapping a [TextView].
+ */
+class NormalTextViewWrapper(
+ override val view: TextView
+) : TextViewWrapper {
+ override var text: CharSequence?
+ get() = view.text
+ set(value) {
+ view.text = value
+ }
+
+ override var textColor: Int
+ get() = view.currentTextColor
+ set(value) {
+ view.setTextColor(value)
+ }
+}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/TimeText.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/TimeText.kt
new file mode 100644
index 0000000000..8d88acddc6
--- /dev/null
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/ui/views/TimeText.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.habitrpg.wearos.habitica.ui.views
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.graphics.Color
+import android.provider.Settings
+import android.text.format.DateFormat
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.FrameLayout
+import androidx.annotation.AttrRes
+import androidx.annotation.StyleRes
+import androidx.annotation.VisibleForTesting
+import androidx.core.content.res.use
+import androidx.core.os.ConfigurationCompat
+import androidx.core.view.isGone
+import com.example.android.wearable.timetext.CurvedTextViewWrapper
+import com.example.android.wearable.timetext.NormalTextViewWrapper
+import com.example.android.wearable.timetext.TextViewWrapper
+import com.habitrpg.android.habitica.R
+import com.habitrpg.android.habitica.databinding.CurvedTimeTextBinding
+import com.habitrpg.android.habitica.databinding.StraightTimeTextBinding
+import com.habitrpg.wearos.habitica.ui.views.TimeText.Clock
+import com.habitrpg.wearos.habitica.ui.views.TimeTextViewBinding.TimeTextCurvedViewBinding
+import com.habitrpg.wearos.habitica.ui.views.TimeTextViewBinding.TimeTextStraightViewBinding
+import java.util.Calendar
+
+/**
+ * The max sweep angle for the [TimeText] to occupy.
+ */
+private const val MAX_SWEEP_ANGLE = 90f
+
+class TimeText @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ @AttrRes defStyleAttr: Int = 0,
+ @StyleRes defStyleRes: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+ /**
+ * The underlying [Calendar] instance for producing the time.
+ *
+ * This will be updated in [onTimeZoneChange] in response to any timezone updates.
+ */
+ private var time = Calendar.getInstance()
+
+ /**
+ * True if we should format the time in the 24 hour manner.
+ *
+ * This will be updated in [onTimeFormatChange] in response to any format updates.
+ */
+ private var use24HourFormat = DateFormat.is24HourFormat(context)
+
+ /**
+ * An [IntentFilter] for any time related broadcast.
+ */
+ private val timeBroadcastReceiverFilter = IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ addAction(Intent.ACTION_TIMEZONE_CHANGED)
+ }
+
+ private val timeBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ when (intent.action) {
+ Intent.ACTION_TIMEZONE_CHANGED -> onTimeZoneChange()
+ Intent.ACTION_TIME_TICK, Intent.ACTION_TIME_CHANGED -> onTimeChange()
+ }
+ }
+ }
+
+ /**
+ * The wrapped view binding for the inflated views.
+ */
+ private val timeTextViewBinding: TimeTextViewBinding
+
+ /**
+ * A non-clock portion of the time text to display.
+ */
+ var title: CharSequence? = null
+ set(value) {
+ field = value
+
+ timeTextViewBinding.timeTextTitle.text = title
+
+ // Only show the title and divider if the title is non-empty
+ val hideTitle = title.isNullOrEmpty()
+ timeTextViewBinding.timeTextTitle.view.isGone = hideTitle
+ timeTextViewBinding.timeTextDivider.view.isGone = hideTitle
+ }
+
+ /**
+ * The color of the non-clock portion of the time text.
+ */
+ var titleTextColor: Int = Color.WHITE
+ set(value) {
+ field = value
+
+ timeTextViewBinding.timeTextTitle.textColor = titleTextColor
+ }
+
+ /**
+ * The backing [Clock] used to drive the time.
+ *
+ * Overridable for testing.
+ */
+ @VisibleForTesting
+ var clock: Clock = Clock(System::currentTimeMillis)
+ set(value) {
+ field = value
+ onTimeChange()
+ }
+
+ /**
+ * The [ContentObserver] listening for a time format change.
+ *
+ * This is constructed lazily, since [getHandler] needs the view to be attached.
+ */
+ private val timeContentObserver by lazy(LazyThreadSafetyMode.NONE) {
+ object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ super.onChange(selfChange)
+ onTimeFormatChange()
+ }
+ }
+ }
+
+ init {
+ val layoutInflater = LayoutInflater.from(context)
+
+ // Create the view structure based on whether the screen is round.
+ // This will inflate one of two distinct layouts, which we abstract away in a TimeTextViewBinding
+ timeTextViewBinding = if (resources.configuration.isScreenRound) {
+ TimeTextCurvedViewBinding(CurvedTimeTextBinding.inflate(layoutInflater, this, true))
+ } else {
+ TimeTextStraightViewBinding(StraightTimeTextBinding.inflate(layoutInflater, this, true))
+ }
+
+ // Set the divider text
+ timeTextViewBinding.timeTextDivider.text = "·"
+
+ // Update based on the styled attributes.
+ // Note that this runs the side-effects of setting those attributes.
+ context.obtainStyledAttributes(attrs, R.styleable.TimeText, defStyleAttr, defStyleRes)
+ .use { typedArray ->
+ titleTextColor =
+ typedArray.getColor(R.styleable.TimeText_android_titleTextColor, titleTextColor)
+ title = typedArray.getString(R.styleable.TimeText_titleText)
+ }
+ }
+
+ /**
+ * Restrict the total sweep angle on round screens to [MAX_SWEEP_ANGLE].
+ *
+ * We accomplish this with two measure passes:
+ *
+ * After the first, we measure to get the angle that the clock and divider occupy (together, these shouldn't ever
+ * be more than [MAX_SWEEP_ANGLE].
+ *
+ * Then, we update the title's max sweep angle to the remaining angle, and measure again to apply the limit.
+ */
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ when (timeTextViewBinding) {
+ is TimeTextCurvedViewBinding -> {
+ // Reset the title sweep to ensure we get a true initial measurement
+ timeTextViewBinding.timeTextTitle.view.setSweepRangeDegrees(0f, MAX_SWEEP_ANGLE)
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val clockSweepAngle =
+ timeTextViewBinding.timeTextClock.view.sweepAngleDegrees.coerceAtLeast(0f)
+
+ // Avoid getting the divider sweep angle if it is gone, since it won't be accurate
+ val dividerSweepAngle = if (timeTextViewBinding.timeTextDivider.view.isGone) {
+ 0f
+ } else {
+ timeTextViewBinding.timeTextDivider.view.sweepAngleDegrees.coerceAtLeast(0f)
+ }
+
+ val maxTitleSweepAngle = MAX_SWEEP_ANGLE - clockSweepAngle - dividerSweepAngle
+
+ // Update the title max sweep angle to effectively get a total max sweep of MAX_SWEEP_ANGLE
+ timeTextViewBinding.timeTextTitle.view.setSweepRangeDegrees(0f, maxTitleSweepAngle)
+
+ // Measure again, with the updated max sweep
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+ is TimeTextStraightViewBinding -> {
+ // Need to do nothing special the for the straight view, just call through to super
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+ }
+ }
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ onTimeZoneChange()
+ onTimeFormatChange()
+ onTimeChange()
+
+ context.contentResolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.TIME_12_24),
+ true,
+ timeContentObserver
+ )
+ context.registerReceiver(timeBroadcastReceiver, timeBroadcastReceiverFilter)
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+
+ context.contentResolver.unregisterContentObserver(timeContentObserver)
+ context.unregisterReceiver(timeBroadcastReceiver)
+ }
+
+ private fun onTimeChange() {
+ val pattern = DateFormat.getBestDateTimePattern(
+ ConfigurationCompat.getLocales(resources.configuration)[0],
+ if (use24HourFormat) "Hm" else "hm"
+ )
+ // Remove the am/pm indicator (if any). This is locale safe.
+ val patternWithoutAmPm = pattern.replace("a", "").trim()
+
+ time.timeInMillis = clock.getCurrentTimeMillis()
+ timeTextViewBinding.timeTextClock.text = DateFormat.format(patternWithoutAmPm, time)
+ }
+
+ private fun onTimeZoneChange() {
+ time = Calendar.getInstance()
+ onTimeChange()
+ }
+
+ private fun onTimeFormatChange() {
+ use24HourFormat = DateFormat.is24HourFormat(context)
+ onTimeChange()
+ }
+
+ /**
+ * A provider of the current time.
+ */
+ fun interface Clock {
+
+ /**
+ * Returns the current time in milliseconds since the epoch.
+ */
+ fun getCurrentTimeMillis(): Long
+ }
+}
+
+/**
+ * An abstraction around the view binding, since we inflate two different layouts depending on the shape of the screen.
+ */
+private sealed class TimeTextViewBinding {
+
+ abstract val timeTextTitle: TextViewWrapper
+
+ abstract val timeTextDivider: TextViewWrapper
+
+ abstract val timeTextClock: TextViewWrapper
+
+ /**
+ * The [TimeTextViewBinding] wrapping the [CurvedTimeTextBinding].
+ */
+ class TimeTextCurvedViewBinding(
+ timeTextBinding: CurvedTimeTextBinding
+ ) : TimeTextViewBinding() {
+ override val timeTextTitle: CurvedTextViewWrapper =
+ CurvedTextViewWrapper(timeTextBinding.timeTextTitle)
+ override val timeTextDivider: CurvedTextViewWrapper =
+ CurvedTextViewWrapper(timeTextBinding.timeTextDivider)
+ override val timeTextClock: CurvedTextViewWrapper =
+ CurvedTextViewWrapper(timeTextBinding.timeTextClock)
+ }
+
+ /**
+ * The [TimeTextViewBinding] wrapping the [StraightTimeTextBinding].
+ */
+ class TimeTextStraightViewBinding(
+ timeTextBinding: StraightTimeTextBinding
+ ) : TimeTextViewBinding() {
+ override val timeTextTitle: NormalTextViewWrapper =
+ NormalTextViewWrapper(timeTextBinding.timeTextTitle)
+ override val timeTextDivider: NormalTextViewWrapper =
+ NormalTextViewWrapper(timeTextBinding.timeTextDivider)
+ override val timeTextClock: NormalTextViewWrapper =
+ NormalTextViewWrapper(timeTextBinding.timeTextClock)
+ }
+}
diff --git a/wearos/src/main/java/com/habitrpg/wearos/habitica/util/TopScrollAwayBehavior.kt b/wearos/src/main/java/com/habitrpg/wearos/habitica/util/TopScrollAwayBehavior.kt
new file mode 100644
index 0000000000..a24facab52
--- /dev/null
+++ b/wearos/src/main/java/com/habitrpg/wearos/habitica/util/TopScrollAwayBehavior.kt
@@ -0,0 +1,53 @@
+package com.habitrpg.wearos.habitica.util
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.view.ViewCompat
+import kotlin.math.abs
+import kotlin.math.min
+
+class TopScrollAwayBehavior(context: Context, attrs: AttributeSet) :
+ CoordinatorLayout.Behavior(context, attrs) {
+
+ override fun onStartNestedScroll(
+ coordinatorLayout: CoordinatorLayout,
+ child: V,
+ directTargetChild: View,
+ target: View,
+ axes: Int,
+ type: Int
+ ): Boolean {
+ return axes == ViewCompat.SCROLL_AXIS_VERTICAL
+ }
+
+ override fun onNestedPreScroll(
+ coordinatorLayout: CoordinatorLayout,
+ child: V,
+ target: View,
+ dx: Int,
+ dy: Int,
+ consumed: IntArray,
+ type: Int
+ ) {
+ super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
+ child.translationY = min(0f, -min(child.height.toFloat(), -child.translationY + dy))
+ }
+
+ override fun onStopNestedScroll(
+ coordinatorLayout: CoordinatorLayout,
+ child: V,
+ target: View,
+ type: Int
+ ) {
+ super.onStopNestedScroll(coordinatorLayout, child, target, type)
+ if (child.translationY != 0f && abs(child.translationY) != child.height.toFloat()) {
+ if (abs(child.translationY) < (child.height.toFloat() / 2f) && abs(child.translationY) < 40) {
+ child.translationY = 0f
+ } else {
+ child.translationY = if (child.top < child.bottom) -child.height.toFloat() else child.height.toFloat()
+ }
+ }
+ }
+}
diff --git a/wearos/src/main/res/drawable/splash_screen.xml b/wearos/src/main/res/drawable/splash_screen.xml
new file mode 100644
index 0000000000..1b8f7b680e
--- /dev/null
+++ b/wearos/src/main/res/drawable/splash_screen.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/wearos/src/main/res/layout/activity_login.xml b/wearos/src/main/res/layout/activity_login.xml
index d262a60745..8d543f4b03 100644
--- a/wearos/src/main/res/layout/activity_login.xml
+++ b/wearos/src/main/res/layout/activity_login.xml
@@ -1,9 +1,15 @@
-
+
+ android:layout_marginBottom="@dimen/spacing_medium"
+ android:layout_marginTop="24dp"/>
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
diff --git a/wearos/src/main/res/layout/activity_main.xml b/wearos/src/main/res/layout/activity_main.xml
index ddd9427f27..0958a63682 100644
--- a/wearos/src/main/res/layout/activity_main.xml
+++ b/wearos/src/main/res/layout/activity_main.xml
@@ -1,11 +1,21 @@
-
-
-
\ No newline at end of file
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+
+
+
diff --git a/wearos/src/main/res/layout/activity_rya.xml b/wearos/src/main/res/layout/activity_rya.xml
index de550b7c89..141fa90aff 100644
--- a/wearos/src/main/res/layout/activity_rya.xml
+++ b/wearos/src/main/res/layout/activity_rya.xml
@@ -9,7 +9,8 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:visibility="gone">
+ android:visibility="gone"
+ android:scrollbars="vertical">
-
\ No newline at end of file
+
diff --git a/wearos/src/main/res/layout/activity_settings.xml b/wearos/src/main/res/layout/activity_settings.xml
index 07afd292e7..a03e5c7367 100644
--- a/wearos/src/main/res/layout/activity_settings.xml
+++ b/wearos/src/main/res/layout/activity_settings.xml
@@ -1,10 +1,21 @@
-
-
-
\ No newline at end of file
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+
+
+
diff --git a/wearos/src/main/res/layout/activity_splash.xml b/wearos/src/main/res/layout/activity_splash.xml
index c96a44138b..ed956e78ad 100644
--- a/wearos/src/main/res/layout/activity_splash.xml
+++ b/wearos/src/main/res/layout/activity_splash.xml
@@ -1,17 +1,21 @@
-
-
-
\ No newline at end of file
+
+
+
+
+
diff --git a/wearos/src/main/res/layout/activity_task_detail.xml b/wearos/src/main/res/layout/activity_task_detail.xml
index df42ef61b6..7548fcaec5 100644
--- a/wearos/src/main/res/layout/activity_task_detail.xml
+++ b/wearos/src/main/res/layout/activity_task_detail.xml
@@ -2,7 +2,8 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:scrollbars="vertical">
-
\ No newline at end of file
+
diff --git a/wearos/src/main/res/layout/activity_task_form.xml b/wearos/src/main/res/layout/activity_task_form.xml
index 8642f66284..4af4fe9530 100644
--- a/wearos/src/main/res/layout/activity_task_form.xml
+++ b/wearos/src/main/res/layout/activity_task_form.xml
@@ -3,7 +3,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
- xmlns:app="http://schemas.android.com/apk/res-auto">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:scrollbars="vertical">
diff --git a/wearos/src/main/res/layout/activity_tasklist.xml b/wearos/src/main/res/layout/activity_tasklist.xml
index 68bf693e64..97047fe1b7 100644
--- a/wearos/src/main/res/layout/activity_tasklist.xml
+++ b/wearos/src/main/res/layout/activity_tasklist.xml
@@ -14,4 +14,10 @@
android:layout_height="39dp"
android:layout_gravity="center|bottom"
app:layout_behavior="com.habitrpg.wearos.habitica.util.ScrollAwayBehavior"/>
-
\ No newline at end of file
+
+
diff --git a/wearos/src/main/res/layout/curved_time_text.xml b/wearos/src/main/res/layout/curved_time_text.xml
new file mode 100644
index 0000000000..780000d3db
--- /dev/null
+++ b/wearos/src/main/res/layout/curved_time_text.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wearos/src/main/res/layout/spinner_item.xml b/wearos/src/main/res/layout/spinner_item.xml
deleted file mode 100644
index ef02726967..0000000000
--- a/wearos/src/main/res/layout/spinner_item.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/wearos/src/main/res/layout/stat_value_layout.xml b/wearos/src/main/res/layout/stat_value_layout.xml
index ddaad3dfcd..80dbfabe51 100644
--- a/wearos/src/main/res/layout/stat_value_layout.xml
+++ b/wearos/src/main/res/layout/stat_value_layout.xml
@@ -35,7 +35,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="2dp"
- android:autoSizeMaxTextSize="16sp"
+ android:autoSizeMaxTextSize="20sp"
android:autoSizeMinTextSize="12sp"
android:autoSizeStepGranularity="2sp"
android:autoSizeTextType="uniform"
@@ -51,7 +51,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
- android:autoSizeMaxTextSize="6sp"
+ android:autoSizeMaxTextSize="10sp"
android:autoSizeMinTextSize="4sp"
android:autoSizeTextType="uniform"
android:fontFamily="@font/press_start_reg"
@@ -62,4 +62,4 @@
app:layout_constraintStart_toEndOf="@+id/view"
app:layout_constraintTop_toTopOf="parent" />
-
\ No newline at end of file
+
diff --git a/wearos/src/main/res/layout/straight_time_text.xml b/wearos/src/main/res/layout/straight_time_text.xml
new file mode 100644
index 0000000000..6a47a77e58
--- /dev/null
+++ b/wearos/src/main/res/layout/straight_time_text.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wearos/src/main/res/values-pt-rBR/strings.xml b/wearos/src/main/res/values-pt-rBR/strings.xml
index 4118776e1e..4ddd8c5dd7 100644
--- a/wearos/src/main/res/values-pt-rBR/strings.xml
+++ b/wearos/src/main/res/values-pt-rBR/strings.xml
@@ -12,7 +12,6 @@
EntrarEntrar no CelularCriar Conta
- Entrar com senhaContinuarVocê ficou sem VidaVocê perdeu um Nível, Experiência e Ouro. Não desista!
@@ -41,4 +40,4 @@
Começando seu Dia...OKOutras Opções
-
\ No newline at end of file
+
diff --git a/wearos/src/main/res/values-ru/strings.xml b/wearos/src/main/res/values-ru/strings.xml
index 033511a19b..873a01f291 100644
--- a/wearos/src/main/res/values-ru/strings.xml
+++ b/wearos/src/main/res/values-ru/strings.xml
@@ -7,11 +7,10 @@
ВойтиДругие опцииСоздать аккаунт
- Войти с паролемПродолжитьНачало дняНачать новый день
- Введите название задачи
+ Название задачи…СохранитьСинхронизировать данныеСкрыть награды за задание
@@ -41,4 +40,4 @@
Немного едыНачните свой день...Имеются невыполненные ежедневные дела за вчерашний день
-
\ No newline at end of file
+
diff --git a/wearos/src/main/res/values/attrs.xml b/wearos/src/main/res/values/attrs.xml
index 7ee35e9b58..99b6a8a7a4 100644
--- a/wearos/src/main/res/values/attrs.xml
+++ b/wearos/src/main/res/values/attrs.xml
@@ -19,4 +19,8 @@
-
\ No newline at end of file
+
+
+
+
+
diff --git a/wearos/src/main/res/values/dimens.xml b/wearos/src/main/res/values/dimens.xml
index 881842e312..2589494408 100644
--- a/wearos/src/main/res/values/dimens.xml
+++ b/wearos/src/main/res/values/dimens.xml
@@ -17,4 +17,5 @@
6dp10dp2dp
-
\ No newline at end of file
+ 48dp
+
diff --git a/wearos/src/main/res/values/strings.xml b/wearos/src/main/res/values/strings.xml
index 5971df14dd..b7b3772483 100644
--- a/wearos/src/main/res/values/strings.xml
+++ b/wearos/src/main/res/values/strings.xml
@@ -13,7 +13,6 @@
Sign in on phoneOther optionsCreate account
- Sign in with passwordContinueYou ran out of HPYou lost a Level, Exp, and Gold. Don’t give up!
@@ -40,4 +39,4 @@
All done today!Starting your Day...Disconnected
-
\ No newline at end of file
+
diff --git a/wearos/src/main/res/values/styles.xml b/wearos/src/main/res/values/styles.xml
index 02945d79b7..fb69f728a1 100644
--- a/wearos/src/main/res/values/styles.xml
+++ b/wearos/src/main/res/values/styles.xml
@@ -4,6 +4,17 @@
@color/watch_blacksans-serif
+
+
+
-
\ No newline at end of file
+
+
+