Merge branch 'main' into hafiz/settings-updates

This commit is contained in:
Phillip Thelen 2022-06-30 09:37:24 +02:00 committed by GitHub
commit da7ee14430
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
119 changed files with 886 additions and 365 deletions

View file

@ -44,31 +44,31 @@ jobs:
with: with:
arguments: testProdDebugUnitTest arguments: testProdDebugUnitTest
ui-test: # ui-test:
runs-on: macos-latest # runs-on: macos-latest
strategy: # strategy:
matrix: # matrix:
api-level: [24, 26, 28, 29, 30, 31] # api-level: [24, 26, 28, 29, 30, 31]
steps: # steps:
- uses: actions/checkout@v2 # - uses: actions/checkout@v2
- name: set up JDK 11 # - name: set up JDK 11
uses: actions/setup-java@v2 # uses: actions/setup-java@v2
with: # with:
java-version: '11' # java-version: '11'
distribution: 'adopt' # distribution: 'adopt'
cache: gradle # cache: gradle
- name: Prepare # - name: Prepare
run: ./.github/prepare-workflow # run: ./.github/prepare-workflow
- name: Validate Gradle wrapper # - name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b # uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
- name: run tests # - name: run tests
uses: reactivecircus/android-emulator-runner@v2 # uses: reactivecircus/android-emulator-runner@v2
with: # with:
api-level: ${{ matrix.api-level }} # api-level: ${{ matrix.api-level }}
arch: x86_64 # arch: x86_64
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true # disable-animations: true
script: ./gradlew connectedProdDebugAndroidTest # script: ./gradlew connectedProdDebugAndroidTest
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -150,7 +150,7 @@ android {
buildConfigField "String", "TESTING_LEVEL", "\"production\"" buildConfigField "String", "TESTING_LEVEL", "\"production\""
resConfigs 'en', 'bg', 'de', 'en-rGB', 'es', 'fr', 'hr-rHR', 'in', 'it', 'iw', 'ja', 'ko', 'lt', 'nl', 'pl', 'pt-rBR', 'pt-rPT', 'ru', 'tr', 'zh', 'zh-rTW' resConfigs 'en', 'bg', 'de', 'en-rGB', 'es', 'fr', 'hr-rHR', 'in', 'it', 'iw', 'ja', 'ko', 'lt', 'nl', 'pl', 'pt-rBR', 'pt-rPT', 'ru', 'tr', 'zh', 'zh-rTW'
versionCode 4000 versionCode app_version_code
versionName app_version_name versionName app_version_name
targetSdkVersion target_sdk targetSdkVersion target_sdk
@ -212,22 +212,26 @@ android {
dimension "buildType" dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"staff\"" buildConfigField "String", "TESTING_LEVEL", "\"staff\""
resValue "string", "app_name", "Habitica Staff" resValue "string", "app_name", "Habitica Staff"
versionCode app_version_code + 6
} }
alpha { alpha {
dimension "buildType" dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"alpha\"" buildConfigField "String", "TESTING_LEVEL", "\"alpha\""
resValue "string", "app_name", "Habitica Alpha" resValue "string", "app_name", "Habitica Alpha"
versionCode app_version_code + 4
} }
beta { beta {
buildConfigField "String", "TESTING_LEVEL", "\"beta\"" buildConfigField "String", "TESTING_LEVEL", "\"beta\""
dimension "buildType" dimension "buildType"
versionCode app_version_code + 2
} }
prod { prod {
buildConfigField "String", "TESTING_LEVEL", "\"production\"" buildConfigField "String", "TESTING_LEVEL", "\"production\""
dimension "buildType" dimension "buildType"
versionCode app_version_code
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -4,8 +4,7 @@
<dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">8dp</dimen> <dimen name="activity_vertical_margin">8dp</dimen>
<dimen name="card_medium_text">18.0sp</dimen>
<dimen name="card_small_text">14.0sp</dimen>
<dimen name="card_padding">12dp</dimen> <dimen name="card_padding">12dp</dimen>
<dimen name="card_padding_compact">6dp</dimen> <dimen name="card_padding_compact">6dp</dimen>
<dimen name="section_leftright_padding">6dp</dimen> <dimen name="section_leftright_padding">6dp</dimen>

View file

@ -270,7 +270,6 @@
&#8226; Bosses wont do damage for your missed Dailies\n &#8226; Bosses wont do damage for your missed Dailies\n
&#8226; Your boss damage or collection quest items will stay pending until check-out</string> &#8226; Your boss damage or collection quest items will stay pending until check-out</string>
<string name="empty_items">You don\'t have any %s</string> <string name="empty_items">You don\'t have any %s</string>
<string name="user_level">Lvl %d</string>
<string name="user_level_with_class">Lvl %1$d %2$s</string> <string name="user_level_with_class">Lvl %1$d %2$s</string>
<string name="user_level_with_class_unabbreviated">Level %1$d %2$s</string> <string name="user_level_with_class_unabbreviated">Level %1$d %2$s</string>
<string name="warrior">Warrior</string> <string name="warrior">Warrior</string>
@ -1138,9 +1137,7 @@
<string name="locked_equipment_shop_dialog">You must purchase the previous items in this sequence to unlock</string> <string name="locked_equipment_shop_dialog">You must purchase the previous items in this sequence to unlock</string>
<string name="caused_damage">You caused damage to the boss</string> <string name="caused_damage">You caused damage to the boss</string>
<string name="avatar_style">Style</string> <string name="avatar_style">Style</string>
<string name="failed">Failed to load</string>
<string name="daily_item_checkbox">Daily Checkbox</string> <string name="daily_item_checkbox">Daily Checkbox</string>
<string name="retry">Retry</string>
<string name="empty_title">Nothing here yet</string> <string name="empty_title">Nothing here yet</string>
<string name="empty_equipment_description">You don\'t have any equipment of this type yet. You can purchase equipment from the market using the gold you earned.</string> <string name="empty_equipment_description">You don\'t have any equipment of this type yet. You can purchase equipment from the market using the gold you earned.</string>
<string name="todo_item_checkbox">Todo Checkbox</string> <string name="todo_item_checkbox">Todo Checkbox</string>

View file

@ -69,7 +69,7 @@ class ChallengeRepositoryImpl(
for ((key, value) in stringListMap) { for ((key, value) in stringListMap) {
val taskIdList = value.map { t -> t.id ?: "" } val taskIdList = value.map { t -> t.id ?: "" }
if (key == null) continue
when (key) { when (key) {
TaskType.HABIT -> tasksOrder.habits = taskIdList TaskType.HABIT -> tasksOrder.habits = taskIdList
TaskType.DAILY -> tasksOrder.dailys = taskIdList TaskType.DAILY -> tasksOrder.dailys = taskIdList

View file

@ -5,14 +5,14 @@ import androidx.appcompat.app.AppCompatActivity
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.executors.PostExecutionThread import com.habitrpg.android.habitica.executors.PostExecutionThread
import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject
class DisplayItemDropUseCase @Inject class DisplayItemDropUseCase @Inject
constructor(private val soundManager: SoundManager, postExecutionThread: PostExecutionThread) : UseCase<DisplayItemDropUseCase.RequestValues, Void>(postExecutionThread) { constructor(private val soundManager: SoundManager, postExecutionThread: PostExecutionThread) : UseCase<DisplayItemDropUseCase.RequestValues, Void>(postExecutionThread) {
@ -22,14 +22,14 @@ constructor(private val soundManager: SoundManager, postExecutionThread: PostExe
val data = requestValues.data val data = requestValues.data
val snackbarText = StringBuilder(data?.drop?.dialog ?: "") val snackbarText = StringBuilder(data?.drop?.dialog ?: "")
if (data?.questItemsFound ?: 0 > 0 && requestValues.showQuestItems) { if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) {
if (snackbarText.isNotEmpty()) if (snackbarText.isNotEmpty())
snackbarText.append('\n') snackbarText.append('\n')
snackbarText.append(requestValues.context.getString(R.string.quest_items_found, data!!.questItemsFound)) snackbarText.append(requestValues.context.getString(R.string.quest_items_found, data!!.questItemsFound))
} }
if (snackbarText.isNotEmpty()) { if (snackbarText.isNotEmpty()) {
GlobalScope.launch(context = Dispatchers.Main) { MainScope().launch(context = Dispatchers.Main) {
delay(3000L) delay(3000L)
HabiticaSnackbar.showSnackbar( HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView, requestValues.snackbarTargetView,

View file

@ -1,11 +1,11 @@
package com.habitrpg.android.habitica.interactors package com.habitrpg.android.habitica.interactors
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskDirectionData
import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskDirectionData
import com.habitrpg.common.habitica.models.tasks.TaskType
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToLong import kotlin.math.roundToLong
@ -46,6 +46,9 @@ class ScoreTaskLocallyInteractor {
private fun scoreToDo(user: User, task: Task, direction: TaskDirection) { private fun scoreToDo(user: User, task: Task, direction: TaskDirection) {
} }
private fun scoreReward(user: User, task: Task, direction: TaskDirection) {
}
fun score(user: User, task: Task, direction: TaskDirection): TaskDirectionData? { fun score(user: User, task: Task, direction: TaskDirection): TaskDirectionData? {
return if (task.type == TaskType.HABIT || direction == TaskDirection.UP) { return if (task.type == TaskType.HABIT || direction == TaskDirection.UP) {
val stats = user.stats ?: return null val stats = user.stats ?: return null
@ -67,14 +70,16 @@ class ScoreTaskLocallyInteractor {
TaskType.HABIT -> scoreHabit(user, task, direction) TaskType.HABIT -> scoreHabit(user, task, direction)
TaskType.DAILY -> scoreDaily(user, task, direction) TaskType.DAILY -> scoreDaily(user, task, direction)
TaskType.TODO -> scoreToDo(user, task, direction) TaskType.TODO -> scoreToDo(user, task, direction)
TaskType.REWARD -> scoreReward(user, task, direction)
else -> {}
} }
if (result.hp <= 0.0) { if (result.hp <= 0.0) {
result.hp = 0.0 result.hp = 0.0
} }
if (result.exp >= stats.toNextLevel?.toDouble() ?: 0.0) { if (result.exp >= (stats.toNextLevel?.toDouble() ?: 0.0)) {
result.exp = result.exp - (stats.toNextLevel?.toDouble() ?: 0.0) result.exp = result.exp - (stats.toNextLevel?.toDouble() ?: 0.0)
result.lvl = user.stats?.lvl ?: 0 + 1 result.lvl = (user.stats?.lvl ?: 0) + 1
result.hp = 50.0 result.hp = 50.0
} else { } else {
result.lvl = user.stats?.lvl ?: 0 result.lvl = user.stats?.lvl ?: 0

View file

@ -29,7 +29,6 @@ import java.util.Date
import java.util.GregorianCalendar import java.util.GregorianCalendar
open class Task : RealmObject, BaseMainObject, Parcelable { open class Task : RealmObject, BaseMainObject, Parcelable {
override val realmClass: Class<Task> override val realmClass: Class<Task>
get() = Task::class.java get() = Task::class.java
override val primaryIdentifier: String? override val primaryIdentifier: String?
@ -109,6 +108,21 @@ open class Task : RealmObject, BaseMainObject, Parcelable {
val completedChecklistCount: Int val completedChecklistCount: Int
get() = checklist?.count { it.completed } ?: 0 get() = checklist?.count { it.completed } ?: 0
val streakString: String?
get() {
return if (counterUp != null && (counterUp ?: 0) > 0 && counterDown != null && (counterDown ?: 0) > 0) {
"+" + counterUp.toString() + " | -" + counterDown?.toString()
} else if (counterUp != null && (counterUp ?: 0) > 0) {
"+" + counterUp.toString()
} else if (counterDown != null && (counterDown ?: 0) > 0) {
"-" + counterDown.toString()
} else if ((streak ?: 0) > 0) {
return streak.toString()
} else {
null
}
}
val extraLightTaskColor: Int val extraLightTaskColor: Int
get() { get() {
return when { return when {

View file

@ -31,7 +31,7 @@ import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Stats import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel
import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewState import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog

View file

@ -496,7 +496,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
private fun checkMaintenance() { private fun checkMaintenance() {
viewModel.ifNeedsMaintenance { maintenanceResponse -> viewModel.ifNeedsMaintenance { maintenanceResponse ->
if (maintenanceResponse.activeMaintenance) { if (maintenanceResponse.activeMaintenance == true) {
val intent = createMaintenanceIntent(maintenanceResponse, false) val intent = createMaintenanceIntent(maintenanceResponse, false)
startActivity(intent) startActivity(intent)
} else { } else {
@ -504,7 +504,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
try { try {
val packageInfo = packageManager.getPackageInfo(packageName, 0) val packageInfo = packageManager.getPackageInfo(packageName, 0)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
if (packageInfo.versionCode < maintenanceResponse.minBuild) { if (packageInfo.versionCode < (maintenanceResponse.minBuild ?: 0)) {
val intent = createMaintenanceIntent(maintenanceResponse, true) val intent = createMaintenanceIntent(maintenanceResponse, true)
startActivity(intent) startActivity(intent)
} }

View file

@ -72,7 +72,7 @@ class MaintenanceActivity : BaseActivity() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
{ maintenanceResponse -> { maintenanceResponse ->
if (!maintenanceResponse.activeMaintenance) { if (maintenanceResponse.activeMaintenance == false) {
finish() finish()
} }
}, },

View file

@ -8,7 +8,6 @@ import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ItemItemBinding import com.habitrpg.android.habitica.databinding.ItemItemBinding
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.Food import com.habitrpg.android.habitica.models.inventory.Food
import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.HatchingPotion
@ -20,10 +19,11 @@ import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.models.user.OwnedPet import com.habitrpg.android.habitica.models.user.OwnedPet
import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu import com.habitrpg.android.habitica.ui.menu.BottomSheetMenu
import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem import com.habitrpg.android.habitica.ui.menu.BottomSheetMenuItem
import com.habitrpg.android.habitica.ui.views.dialogs.DetailDialog import com.habitrpg.android.habitica.ui.views.dialogs.DetailDialog
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.common.habitica.extensions.loadImage
import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.PublishSubject
@ -105,7 +105,7 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
hatchingItem?.key + "-" + item?.key hatchingItem?.key + "-" + item?.key
} }
val pet = existingPets?.firstOrNull { it.key == petKey && it.type != "special" } val pet = existingPets?.firstOrNull { it.key == petKey && it.type != "special" }
return pet != null && ownedPets?.get(pet.key)?.trained ?: 0 <= 0 return pet != null && (ownedPets?.get(pet.key)?.trained ?: 0) <= 0
} }
init { init {
@ -179,9 +179,9 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
} }
} else if (item is SpecialItem) { } else if (item is SpecialItem) {
val specialItem = item as SpecialItem val specialItem = item as SpecialItem
if (specialItem.isMysteryItem && ownedItem?.numberOwned ?: 0 > 0) { if (specialItem.isMysteryItem && (ownedItem?.numberOwned ?: 0) > 0) {
menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.open))) menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.open)))
} else if (ownedItem?.numberOwned ?: 0 > 0) { } else if ((ownedItem?.numberOwned ?: 0) > 0) {
menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.use_item))) menu.addMenuItem(BottomSheetMenuItem(resources.getString(R.string.use_item)))
} }
} }

View file

@ -15,7 +15,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter import com.habitrpg.android.habitica.ui.adapter.inventory.EquipmentRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import javax.inject.Inject import javax.inject.Inject

View file

@ -31,7 +31,7 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseDialogFragment import com.habitrpg.android.habitica.ui.fragments.BaseDialogFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel

View file

@ -37,7 +37,7 @@ import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity import com.habitrpg.android.habitica.ui.activities.SkillMemberActivity
import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter import com.habitrpg.android.habitica.ui.adapter.inventory.ItemRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel

View file

@ -21,7 +21,7 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.android.habitica.ui.helpers.RecyclerViewState import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyViews import com.habitrpg.android.habitica.ui.views.CurrencyViews

View file

@ -19,7 +19,7 @@ import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.android.habitica.models.inventory.HatchingPotion import com.habitrpg.android.habitica.models.inventory.HatchingPotion
import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter import com.habitrpg.android.habitica.ui.adapter.inventory.StableRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.MarginDecoration import com.habitrpg.android.habitica.ui.helpers.MarginDecoration
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel

View file

@ -24,21 +24,21 @@ import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.members.Member import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.social.Challenge import com.habitrpg.android.habitica.models.social.Challenge
import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity import com.habitrpg.android.habitica.ui.activities.ChallengeFormActivity
import com.habitrpg.android.habitica.ui.activities.FullProfileActivity import com.habitrpg.android.habitica.ui.activities.FullProfileActivity
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
import com.habitrpg.common.habitica.helpers.EmojiParser
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.DailyViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.HabitViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.common.habitica.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import javax.inject.Inject import com.habitrpg.common.habitica.helpers.EmojiParser
import com.habitrpg.common.habitica.helpers.setMarkdown
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.common.habitica.views.HabiticaIconsHelper
import retrofit2.HttpException import retrofit2.HttpException
import javax.inject.Inject
class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>() { class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>() {
@ -117,7 +117,8 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
val rewards = ArrayList<Task>() val rewards = ArrayList<Task>()
for (entry in taskList) { for (entry in taskList) {
when (entry.type) { val type = entry.type ?: continue
when (type) {
TaskType.TODO -> todos.add(entry) TaskType.TODO -> todos.add(entry)
TaskType.HABIT -> habits.add(entry) TaskType.HABIT -> habits.add(entry)
TaskType.DAILY -> dailies.add(entry) TaskType.DAILY -> dailies.add(entry)

View file

@ -18,7 +18,7 @@ import com.habitrpg.android.habitica.models.social.Group
import com.habitrpg.android.habitica.modules.AppModule import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter import com.habitrpg.android.habitica.ui.adapter.social.ChallengesListViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.kotlin.Flowables import io.reactivex.rxjava3.kotlin.Flowables

View file

@ -13,7 +13,7 @@ import com.habitrpg.android.habitica.databinding.FragmentRefreshRecyclerviewBind
import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter import com.habitrpg.android.habitica.ui.adapter.social.GuildListAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil import com.habitrpg.android.habitica.ui.helpers.KeyboardUtil
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import javax.inject.Inject import javax.inject.Inject

View file

@ -28,10 +28,7 @@ import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationsManager import com.habitrpg.android.habitica.helpers.NotificationsManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter import com.habitrpg.android.habitica.ui.adapter.BaseRecyclerViewAdapter
@ -41,13 +38,16 @@ import com.habitrpg.android.habitica.ui.adapter.tasks.RewardsRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TaskRecyclerViewAdapter import com.habitrpg.android.habitica.ui.adapter.tasks.TaskRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter import com.habitrpg.android.habitica.ui.adapter.tasks.TodosRecyclerViewAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.EmptyItem import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder import com.habitrpg.android.habitica.ui.viewHolders.tasks.BaseTaskViewHolder
import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel import com.habitrpg.android.habitica.ui.viewmodels.TasksViewModel
import com.habitrpg.common.habitica.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.common.habitica.views.HabiticaIconsHelper
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -471,6 +471,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
viewModel?.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE) viewModel?.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE)
} }
} }
else -> {}
} }
} }
} }
@ -534,6 +535,10 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
fragment.tutorialStepIdentifier = "todos" fragment.tutorialStepIdentifier = "todos"
tutorialTexts = listOf(context.getString(R.string.tutorial_todos_1), context.getString(R.string.tutorial_todos_2)) tutorialTexts = listOf(context.getString(R.string.tutorial_todos_1), context.getString(R.string.tutorial_todos_2))
} }
TaskType.REWARD -> {
fragment.tutorialStepIdentifier = "rewards"
tutorialTexts = listOf(context.getString(R.string.tutorial_rewards_1), context.getString(R.string.tutorial_rewards_2))
}
} }
} }

View file

@ -2,34 +2,18 @@ package com.habitrpg.android.habitica.ui.helpers
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.android.habitica.databinding.EmptyItemBinding import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.android.habitica.databinding.FailedItemBinding import com.habitrpg.common.habitica.helpers.RecyclerViewStateAdapter
import com.habitrpg.android.habitica.extensions.inflate
data class EmptyItem( class RecyclerViewEmptySupport @JvmOverloads constructor(
var title: String, context: Context, attrs: AttributeSet? = null
var text: String? = null, ) : RecyclerView(context, attrs) {
var iconResource: Int? = null, var onRefresh: (() -> Unit)?
var buttonLabel: String? = null, get() = emptyAdapter.onRefresh
var onButtonTap: (() -> Unit)? = null set(value) { emptyAdapter.onRefresh = value }
)
enum class RecyclerViewState {
LOADING,
EMPTY,
DISPLAYING_DATA,
FAILED
}
class RecyclerViewEmptySupport : RecyclerView {
var onRefresh: (() -> Unit)? = null
var state: RecyclerViewState = RecyclerViewState.LOADING var state: RecyclerViewState = RecyclerViewState.LOADING
set(value) { set(value) {
field = value field = value
@ -37,7 +21,7 @@ class RecyclerViewEmptySupport : RecyclerView {
RecyclerViewState.DISPLAYING_DATA -> updateAdapter(actualAdapter) RecyclerViewState.DISPLAYING_DATA -> updateAdapter(actualAdapter)
else -> { else -> {
updateAdapter(emptyAdapter) updateAdapter(emptyAdapter)
emptyAdapter.notifyDataSetChanged() emptyAdapter.state = value
} }
} }
} }
@ -48,53 +32,16 @@ class RecyclerViewEmptySupport : RecyclerView {
} }
} }
var emptyItem: EmptyItem? = null var emptyItem: EmptyItem?
get() = emptyAdapter.emptyItem
set(value) { set(value) {
field = value emptyAdapter.emptyItem = value
emptyAdapter.notifyDataSetChanged()
} }
private var actualAdapter: Adapter<*>? = null private var actualAdapter: Adapter<*>? = null
private val emptyAdapter: Adapter<ViewHolder> = object : Adapter<ViewHolder>() { private val emptyAdapter = RecyclerViewStateAdapter()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
0 -> {
val view = parent.inflate(R.layout.loading_item)
val animation1 = AlphaAnimation(0.0f, 1.0f)
animation1.duration = 300
animation1.startOffset = 500
animation1.fillAfter = true
view.findViewById<ProgressBar>(R.id.loading_indicator).startAnimation(animation1)
object : ViewHolder(view) {}
}
1 -> FailedViewHolder(parent.inflate(R.layout.failed_item))
else -> EmptyViewHolder(parent.inflate(R.layout.empty_item))
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) { private val observer = object : AdapterDataObserver() {
if (holder is EmptyViewHolder) {
holder.bind(emptyItem)
} else if (holder is FailedViewHolder) {
holder.bind(onRefresh)
}
(holder as? EmptyViewHolder)?.bind(emptyItem)
}
override fun getItemCount(): Int {
return 1
}
override fun getItemViewType(position: Int): Int {
return when (state) {
RecyclerViewState.LOADING -> 0
RecyclerViewState.FAILED -> 1
else -> 2
}
}
}
private val observer = object : RecyclerView.AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
updateState() updateState()
} }
@ -108,22 +55,16 @@ class RecyclerViewEmptySupport : RecyclerView {
} }
} }
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
internal fun updateState(isInitial: Boolean = false) { internal fun updateState(isInitial: Boolean = false) {
if (actualAdapter != null && !isInitial) { state = if (actualAdapter != null && !isInitial) {
val emptyViewVisible = actualAdapter?.itemCount == 0 val emptyViewVisible = actualAdapter?.itemCount == 0
if (emptyViewVisible) { if (emptyViewVisible) {
state = RecyclerViewState.EMPTY RecyclerViewState.EMPTY
} else { } else {
state = RecyclerViewState.DISPLAYING_DATA RecyclerViewState.DISPLAYING_DATA
} }
} else { } else {
state = RecyclerViewState.LOADING RecyclerViewState.LOADING
} }
} }
@ -135,38 +76,4 @@ class RecyclerViewEmptySupport : RecyclerView {
actualAdapter = adapter actualAdapter = adapter
updateState(true) updateState(true)
} }
} }
class FailedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = FailedItemBinding.bind(itemView)
fun bind(onRefresh: (() -> Unit)?) {
if (onRefresh != null) {
binding.refreshButton.visibility = View.VISIBLE
binding.refreshButton.setOnClickListener { onRefresh() }
} else {
binding.refreshButton.visibility = View.GONE
}
}
}
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)
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
}
}
}

View file

@ -7,8 +7,8 @@ import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.android.habitica.models.tasks.Task import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.models.responses.TaskDirection
class HabitViewHolder( class HabitViewHolder(
itemView: View, itemView: View,
@ -110,15 +110,8 @@ class HabitViewHolder(
this.btnMinus.isClickable = false this.btnMinus.isClickable = false
} }
var streakString = "" val streakString = task?.streakString
if (data.counterUp != null && data.counterUp ?: 0 > 0 && data.counterDown != null && data.counterDown ?: 0 > 0) { if (streakString?.isNotEmpty() == true) {
streakString = streakString + "+" + data.counterUp.toString() + " | -" + data.counterDown?.toString()
} else if (data.counterUp != null && data.counterUp ?: 0 > 0) {
streakString = streakString + "+" + data.counterUp.toString()
} else if (data.counterDown != null && data.counterDown ?: 0 > 0) {
streakString = streakString + "-" + data.counterDown.toString()
}
if (streakString.isNotEmpty()) {
streakTextView.text = streakString streakTextView.text = streakString
streakTextView.visibility = View.VISIBLE streakTextView.visibility = View.VISIBLE
streakIconView.visibility = View.VISIBLE streakIconView.visibility = View.VISIBLE

View file

@ -4,7 +4,6 @@ import android.content.SharedPreferences
import androidx.core.content.edit import androidx.core.content.edit
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.android.habitica.api.MaintenanceApiService import com.habitrpg.android.habitica.api.MaintenanceApiService
import com.habitrpg.android.habitica.components.UserComponent import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.ContentRepository import com.habitrpg.android.habitica.data.ContentRepository
@ -16,9 +15,10 @@ import com.habitrpg.android.habitica.helpers.TaskAlarmManager
import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManager
import com.habitrpg.android.habitica.models.TutorialStep import com.habitrpg.android.habitica.models.TutorialStep
import com.habitrpg.android.habitica.models.inventory.Egg import com.habitrpg.android.habitica.models.inventory.Egg
import com.habitrpg.common.habitica.models.responses.MaintenanceResponse
import com.habitrpg.android.habitica.proxy.AnalyticsManager import com.habitrpg.android.habitica.proxy.AnalyticsManager
import com.habitrpg.android.habitica.ui.TutorialView import com.habitrpg.android.habitica.ui.TutorialView
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.models.responses.MaintenanceResponse
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.schedulers.Schedulers
import io.realm.kotlin.isValid import io.realm.kotlin.isValid
@ -136,7 +136,7 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
{ maintenanceResponse -> { maintenanceResponse ->
if (maintenanceResponse == null) { if (maintenanceResponse.activeMaintenance == null) {
return@subscribe return@subscribe
} }
onResult(maintenanceResponse) onResult(maintenanceResponse)

View file

@ -63,6 +63,9 @@ class TaskFilterDialog(context: Context, component: UserComponent?) : HabiticaBo
binding.secondTaskFilter.setText(R.string.dated) binding.secondTaskFilter.setText(R.string.dated)
binding.thirdTaskFilter.setText(R.string.completed) binding.thirdTaskFilter.setText(R.string.completed)
} }
TaskType.REWARD -> {
}
} }
setActiveFilter(viewModel.getActiveFilter(value)) setActiveFilter(viewModel.getActiveFilter(value))
} }

View file

@ -184,6 +184,9 @@ class TaskSerializer : JsonSerializer<Task>, JsonDeserializer<Task> {
} }
obj.addProperty("completed", task.completed) obj.addProperty("completed", task.completed)
} }
else -> {
}
} }
return obj return obj

View file

@ -4,9 +4,10 @@ buildscript {
ext { ext {
target_sdk = 32 target_sdk = 32
app_version_name = '4.0' app_version_name = '4.0'
app_version_code = 4010
kotlin_version = '1.6.21' kotlin_version = '1.7.0'
core_ktx_version = '1.8.0' core_ktx_version = '1.8.0'
appcompat_version = '1.4.2' appcompat_version = '1.4.2'
lifecycle_version = '2.4.1' lifecycle_version = '2.4.1'
@ -37,7 +38,7 @@ buildscript {
classpath "io.realm:realm-gradle-plugin:10.10.1" classpath "io.realm:realm-gradle-plugin:10.10.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.19.0" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.19.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0-rc01" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0-rc02"
classpath 'com.google.firebase:perf-plugin:1.4.1' classpath 'com.google.firebase:perf-plugin:1.4.1'
classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerhilt_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerhilt_version"
} }

View file

@ -0,0 +1,118 @@
package com.habitrpg.common.habitica.helpers
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.R
import com.habitrpg.common.habitica.databinding.EmptyItemBinding
import com.habitrpg.common.habitica.databinding.FailedItemBinding
data class EmptyItem(
var title: String,
var text: String? = null,
var iconResource: Int? = null,
var buttonLabel: String? = null,
var onButtonTap: (() -> Unit)? = null
)
enum class RecyclerViewState {
LOADING,
EMPTY,
DISPLAYING_DATA,
FAILED
}
class FailedViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val binding = FailedItemBinding.bind(itemView)
fun bind(onRefresh: (() -> Unit)?) {
if (onRefresh != null) {
binding.refreshButton.visibility = View.VISIBLE
binding.refreshButton.setOnClickListener { onRefresh() }
} else {
binding.refreshButton.visibility = View.GONE
}
}
}
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)
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
}
}
}
class RecyclerViewStateAdapter(val showLoadingAsEmpty: Boolean = false) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var onRefresh: (() -> Unit)? = null
var emptyItem: EmptyItem? = null
set(value) {
field = value
notifyItemChanged(0)
}
var state: RecyclerViewState = RecyclerViewState.LOADING
set(value) {
field = value
notifyItemChanged(0)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
0 -> {
val view = parent.inflate(R.layout.loading_item)
val animation1 = AlphaAnimation(0.0f, 1.0f)
animation1.duration = 300
animation1.startOffset = 500
animation1.fillAfter = true
view.findViewById<ProgressBar>(R.id.loading_indicator).startAnimation(animation1)
object : RecyclerView.ViewHolder(view) {}
}
1 -> FailedViewHolder(parent.inflate(R.layout.failed_item))
else -> EmptyViewHolder(parent.inflate(R.layout.empty_item))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is EmptyViewHolder) {
holder.bind(emptyItem)
} else if (holder is FailedViewHolder) {
holder.bind(onRefresh)
}
(holder as? EmptyViewHolder)?.bind(emptyItem)
}
override fun getItemCount(): Int {
return 1
}
override fun getItemViewType(position: Int): Int {
return when {
state == RecyclerViewState.LOADING && !showLoadingAsEmpty -> 0
state == RecyclerViewState.FAILED -> 1
else -> 2
}
}
}

View file

@ -1,10 +0,0 @@
package com.habitrpg.common.habitica.models.responses;
public class MaintenanceResponse {
public Boolean activeMaintenance;
public Integer minBuild;
public String title;
public String imageUrl;
public String description;
}

View file

@ -0,0 +1,9 @@
package com.habitrpg.common.habitica.models.responses
class MaintenanceResponse {
var activeMaintenance: Boolean? = null
var minBuild: Int? = null
var title: String? = null
var imageUrl: String? = null
var description: String? = null
}

View file

@ -1,5 +1,8 @@
package com.habitrpg.common.habitica.models.responses package com.habitrpg.common.habitica.models.responses
import android.os.Parcel
import android.os.Parcelable
class TaskDirectionDataTemp { class TaskDirectionDataTemp {
var drop: TaskDirectionDataDrop? = null var drop: TaskDirectionDataDrop? = null
@ -12,10 +15,37 @@ class TaskDirectionDataQuest {
var collection: Int = 0 var collection: Int = 0
} }
class TaskDirectionDataDrop { class TaskDirectionDataDrop() : Parcelable {
var value: Int = 0 var value: Int = 0
var key: String? = null var key: String? = null
var type: String? = null var type: String? = null
var dialog: String? = null var dialog: String? = null
constructor(parcel: Parcel) : this() {
value = parcel.readInt()
key = parcel.readString()
type = parcel.readString()
dialog = parcel.readString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(value)
parcel.writeString(key)
parcel.writeString(type)
parcel.writeString(dialog)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<TaskDirectionDataDrop> {
override fun createFromParcel(parcel: Parcel): TaskDirectionDataDrop {
return TaskDirectionDataDrop(parcel)
}
override fun newArray(size: Int): Array<TaskDirectionDataDrop?> {
return arrayOfNulls(size)
}
}
} }

View file

@ -8,10 +8,10 @@ class TaskScoringResult(): Parcelable {
constructor(data: TaskDirectionData, stats: AvatarStats?) : this() { constructor(data: TaskDirectionData, stats: AvatarStats?) : this() {
hasLeveledUp = data.lvl > (stats?.lvl ?: 0) hasLeveledUp = data.lvl > (stats?.lvl ?: 0)
healthDelta = data.hp - (stats?.hp ?: 0.0) healthDelta = data.hp - (stats?.hp ?: 0.0)
if (hasLeveledUp) { experienceDelta = if (hasLeveledUp) {
experienceDelta = (stats?.toNextLevel ?: 0).toDouble() - (stats?.exp ?: 0.0) + data.exp (stats?.toNextLevel ?: 0).toDouble() - (stats?.exp ?: 0.0) + data.exp
} else { } else {
experienceDelta = data.exp - (stats?.exp ?: 0.0) data.exp - (stats?.exp ?: 0.0)
} }
manaDelta = data.mp - (stats?.mp ?: 0.0) manaDelta = data.mp - (stats?.mp ?: 0.0)
goldDelta = data.gp - (stats?.gp ?: 0.0) goldDelta = data.gp - (stats?.gp ?: 0.0)
@ -40,6 +40,7 @@ class TaskScoringResult(): Parcelable {
level = parcel.readValue(Int::class.java.classLoader) as? Int level = parcel.readValue(Int::class.java.classLoader) as? Int
questDamage = parcel.readValue(Double::class.java.classLoader) as? Double questDamage = parcel.readValue(Double::class.java.classLoader) as? Double
questItemsFound = parcel.readValue(Int::class.java.classLoader) as? Int questItemsFound = parcel.readValue(Int::class.java.classLoader) as? Int
drop = parcel.readValue(TaskDirectionDataDrop::class.java.classLoader) as? TaskDirectionDataDrop
} }
override fun writeToParcel(parcel: Parcel, flags: Int) { override fun writeToParcel(parcel: Parcel, flags: Int) {
@ -51,6 +52,7 @@ class TaskScoringResult(): Parcelable {
parcel.writeValue(level) parcel.writeValue(level)
parcel.writeValue(questDamage) parcel.writeValue(questDamage)
parcel.writeValue(questItemsFound) parcel.writeValue(questItemsFound)
parcel.writeValue(drop)
} }
override fun describeContents(): Int { override fun describeContents(): Int {

View file

@ -1,6 +1,15 @@
package com.habitrpg.common.habitica.models.tasks package com.habitrpg.common.habitica.models.tasks
class TasksOrder { class TasksOrder {
fun positionOf(key: String, type: TaskType): Int {
return when (type) {
TaskType.HABIT -> habits.indexOf(key)
TaskType.DAILY -> dailys.indexOf(key)
TaskType.TODO -> todos.indexOf(key)
TaskType.REWARD -> rewards.indexOf(key)
}
}
var habits: List<String> = listOf() var habits: List<String> = listOf()
var dailys: List<String> = listOf() var dailys: List<String> = listOf()
var todos: List<String> = listOf() var todos: List<String> = listOf()

View file

@ -13,7 +13,9 @@ import androidx.core.content.ContextCompat
import com.habitrpg.common.habitica.R import com.habitrpg.common.habitica.R
import com.habitrpg.common.habitica.models.PlayerTier import com.habitrpg.common.habitica.models.PlayerTier
class UsernameLabel(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) { class UsernameLabel @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
private val textView = TextView(context) private val textView = TextView(context)
private val tierIconView = ImageView(context) private val tierIconView = ImageView(context)
@ -54,7 +56,7 @@ class UsernameLabel(context: Context?, attrs: AttributeSet?) : LinearLayout(cont
textViewParams.gravity = Gravity.CENTER_VERTICAL textViewParams.gravity = Gravity.CENTER_VERTICAL
textViewParams.weight = 1.0f textViewParams.weight = 1.0f
addView(textView, textViewParams) addView(textView, textViewParams)
val padding = context?.resources?.getDimension(R.dimen.spacing_small)?.toInt() ?: 0 val padding = context.resources.getDimension(R.dimen.spacing_small).toInt()
textView.setPadding(0, 0, padding, 0) textView.setPadding(0, 0, padding, 0)
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
val iconViewParams = LayoutParams( val iconViewParams = LayoutParams(

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF"
android:alpha="0.8">
<group android:scaleX="1.2"
android:scaleY="1.2"
android:translateX="-2.4"
android:translateY="-2.4">
<path
android:fillColor="@android:color/white"
android:pathData="M11,15h2v2h-2zM11,7h2v6h-2zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

View file

Before

Width:  |  Height:  |  Size: 214 B

After

Width:  |  Height:  |  Size: 214 B

View file

Before

Width:  |  Height:  |  Size: 660 B

After

Width:  |  Height:  |  Size: 660 B

View file

Before

Width:  |  Height:  |  Size: 189 B

After

Width:  |  Height:  |  Size: 189 B

View file

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

View file

Before

Width:  |  Height:  |  Size: 211 B

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

View file

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 159 B

View file

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 479 B

View file

Before

Width:  |  Height:  |  Size: 168 B

After

Width:  |  Height:  |  Size: 168 B

View file

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 194 B

View file

Before

Width:  |  Height:  |  Size: 142 B

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

View file

Before

Width:  |  Height:  |  Size: 217 B

After

Width:  |  Height:  |  Size: 217 B

View file

Before

Width:  |  Height:  |  Size: 887 B

After

Width:  |  Height:  |  Size: 887 B

View file

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

View file

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 320 B

View file

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 291 B

After

Width:  |  Height:  |  Size: 291 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

View file

Before

Width:  |  Height:  |  Size: 453 B

After

Width:  |  Height:  |  Size: 453 B

View file

Before

Width:  |  Height:  |  Size: 322 B

After

Width:  |  Height:  |  Size: 322 B

View file

@ -26,10 +26,10 @@
<TextView <TextView
android:id="@+id/emptyViewDescription" android:id="@+id/emptyViewDescription"
style="@style/Body2"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:textSize="14sp"
android:textColor="@color/text_ternary" android:textColor="@color/text_ternary"
tools:text="No Items" /> tools:text="No Items" />
@ -37,7 +37,6 @@
android:id="@+id/button" android:id="@+id/button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/HabiticaButton.Primary"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="visible"
android:layout_marginTop="@dimen/spacing_large"/> android:layout_marginTop="@dimen/spacing_large"/>

View file

@ -2,24 +2,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<androidx.constraintlayout.utils.widget.ImageFilterView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/baseline_error_outline_black_36dp" android:src="@drawable/failed_loading"
android:tint="@color/text_quad"/> app:tint="@color/text_quad"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/failed" android:text="@string/failed"
style="@style/Headline"
android:textColor="@color/text_primary"/> android:textColor="@color/text_primary"/>
<Button <Button
android:id="@+id/refresh_button" android:id="@+id/refresh_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/HabiticaButton.Primary"
android:text="@string/retry" android:text="@string/retry"
android:layout_marginTop="@dimen/spacing_large" /> android:layout_marginTop="@dimen/spacing_large" />
</LinearLayout> </LinearLayout>

View file

@ -12,4 +12,7 @@
<dimen name="spacing_xlarge">32dp</dimen> <dimen name="spacing_xlarge">32dp</dimen>
<dimen name="icon_size">18dp</dimen> <dimen name="icon_size">18dp</dimen>
<dimen name="card_medium_text">18.0sp</dimen>
<dimen name="card_small_text">14.0sp</dimen>
</resources> </resources>

View file

@ -27,6 +27,7 @@
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="new_task">New Task</string> <string name="new_task">New Task</string>
<string name="avatar">Avatar</string> <string name="avatar">Avatar</string>
<string name="user_level">Lvl. %d</string>
<string name="action_edit">Edit</string> <string name="action_edit">Edit</string>
<string name="action_cancel">Cancel</string> <string name="action_cancel">Cancel</string>
@ -54,6 +55,12 @@
<string name="create_task_title">Create a Task</string> <string name="create_task_title">Create a Task</string>
<string name="complete_task_title">Complete a Task</string> <string name="complete_task_title">Complete a Task</string>
<string name="create_task">Create %s</string> <string name="create_task">Create %s</string>
<string name="new_task_x">New %s</string>
<string name="save_task_x">Save %s</string>
<string name="are_you_sure">Are you sure?</string> <string name="are_you_sure">Are you sure?</string>
<string name="task">Task</string>
<string name="failed">Failed to load</string>
<string name="retry">Retry</string>
</resources> </resources>

View file

@ -0,0 +1,10 @@
# Habitica Server Port
# local instance
# PORT=3000
PORT=80
BASE_URL=https://habitica.com
STAGING_KEY=
ANDROID_TESTING_UUID=
APPLE_AUTH_CLIENT_ID=
DEBUG_USER_ID=
DEBUG_API_KEY=

View file

@ -0,0 +1,4 @@
fabric_key=
facebook_app_id=
amplitude_app_id=
application_ad_id=

View file

@ -13,7 +13,7 @@ android {
applicationId "com.habitrpg.android.habitica" applicationId "com.habitrpg.android.habitica"
minSdk 26 minSdk 26
targetSdk target_sdk targetSdk target_sdk
versionCode 4001 versionCode app_version_code + 1
versionName app_version_name versionName app_version_name
} }
@ -79,6 +79,8 @@ dependencies {
implementation "com.google.dagger:hilt-android:$daggerhilt_version" implementation "com.google.dagger:hilt-android:$daggerhilt_version"
kapt "com.google.dagger:hilt-compiler:$daggerhilt_version" kapt "com.google.dagger:hilt-compiler:$daggerhilt_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
} }
repositories { repositories {
mavenCentral() mavenCentral()

View file

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.habitrpg.android.habitica"> package="com.habitrpg.android.habitica">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-feature android:name="android.hardware.type.watch" /> <uses-feature android:name="android.hardware.type.watch" />

View file

@ -5,11 +5,13 @@ import android.content.Intent
import com.habitrpg.common.habitica.extensions.setupCoil import com.habitrpg.common.habitica.extensions.setupCoil
import com.habitrpg.common.habitica.helpers.MarkdownParser import com.habitrpg.common.habitica.helpers.MarkdownParser
import com.habitrpg.common.habitica.views.HabiticaIconsHelper import com.habitrpg.common.habitica.views.HabiticaIconsHelper
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
import com.habitrpg.wearos.habitica.data.repositories.UserRepository import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.ui.activities.BaseActivity import com.habitrpg.wearos.habitica.ui.activities.BaseActivity
import com.habitrpg.wearos.habitica.ui.activities.FaintActivity import com.habitrpg.wearos.habitica.ui.activities.FaintActivity
import com.habitrpg.wearos.habitica.ui.activities.RYAActivity import com.habitrpg.wearos.habitica.ui.activities.RYAActivity
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -20,6 +22,7 @@ import javax.inject.Inject
class MainApplication : Application() { class MainApplication : Application() {
@Inject lateinit var userRepository: UserRepository @Inject lateinit var userRepository: UserRepository
@Inject lateinit var taskRepository: TaskRepository
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -40,5 +43,13 @@ class MainApplication : Application() {
} }
}.collect() }.collect()
} }
if (userRepository.hasAuthentication) {
MainScope().launch(CoroutineExceptionHandler { coroutineContext, throwable ->
}) {
val user = userRepository.retrieveUser()
taskRepository.retrieveTasks(user?.tasksOrder)
}
}
} }
} }

View file

@ -1,6 +1,8 @@
package com.habitrpg.wearos.habitica.data package com.habitrpg.wearos.habitica.data
import android.content.Context import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import com.amplitude.api.Amplitude import com.amplitude.api.Amplitude
import com.habitrpg.common.habitica.BuildConfig import com.habitrpg.common.habitica.BuildConfig
import com.habitrpg.common.habitica.api.HostConfig import com.habitrpg.common.habitica.api.HostConfig
@ -10,6 +12,7 @@ import com.habitrpg.common.habitica.models.auth.UserAuthSocial
import com.habitrpg.wearos.habitica.models.WearableHabitResponse import com.habitrpg.wearos.habitica.models.WearableHabitResponse
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import okhttp3.Cache import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
@ -34,7 +37,21 @@ class ApiClient @Inject constructor(
buildRetrofit() buildRetrofit()
} }
fun buildRetrofit() { private fun hasNetwork(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = connectivityManager.activeNetwork ?: return false
val actNw =
connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
return when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
}
private fun buildRetrofit() {
val logging = HttpLoggingInterceptor() val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
logging.level = HttpLoggingInterceptor.Level.BODY logging.level = HttpLoggingInterceptor.Level.BODY
@ -44,15 +61,36 @@ class ApiClient @Inject constructor(
val calendar = GregorianCalendar() val calendar = GregorianCalendar()
val timeZone = calendar.timeZone val timeZone = calendar.timeZone
val timezoneOffset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS) val timezoneOffset = -TimeUnit.MINUTES.convert(
timeZone.getOffset(calendar.timeInMillis).toLong(),
val cacheSize: Long = 10 * 1024 * 1024 // 10 MB TimeUnit.MILLISECONDS
)
val cacheSize = (5 * 1024 * 1024).toLong()
val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize) val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize)
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.cache(cache) .cache(cache)
.addInterceptor(logging) .addInterceptor(logging)
.addInterceptor { chain ->
val request = chain.request()
var cacheContol = CacheControl.Builder()
cacheContol = if (request.method == "GET") {
if (hasNetwork(context)) {
cacheContol.maxAge(5, TimeUnit.MINUTES)
} else {
cacheContol.maxAge(1, TimeUnit.DAYS)
.onlyIfCached()
}
} else {
cacheContol.noCache()
.noStore()
}
chain.proceed(request.newBuilder().header(
"Cache-Control",
cacheContol.build().toString()
).build())
}
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val original = chain.request() val original = chain.request()
var builder: Request.Builder = original.newBuilder() var builder: Request.Builder = original.newBuilder()
@ -70,8 +108,16 @@ class ApiClient @Inject constructor(
builder = builder.header("Authorization", "Basic " + BuildConfig.STAGING_KEY) builder = builder.header("Authorization", "Basic " + BuildConfig.STAGING_KEY)
} }
val request = builder.method(original.method, original.body) val request = builder.method(original.method, original.body)
.removeHeader("Pragma")
.build() .build()
chain.proceed(request) val response = chain.proceed(request)
if (request.method == "GET") {
response.newBuilder()
.header("Cache-Control", request.header("Cache-Control") ?: "")
.build()
} else {
response
}
} }
.readTimeout(2400, TimeUnit.SECONDS) .readTimeout(2400, TimeUnit.SECONDS)
.build() .build()
@ -111,6 +157,9 @@ class ApiClient @Inject constructor(
suspend fun runCron() = process(apiService.runCron()) suspend fun runCron() = process(apiService.runCron())
suspend fun getTasks() = process(apiService.getTasks()) suspend fun getTasks() = process(apiService.getTasks())
suspend fun scoreTask(id: String, direction: String) = process(apiService.scoreTask(id, direction)) suspend fun scoreTask(id: String, direction: String) =
process(apiService.scoreTask(id, direction))
suspend fun createTask(task: Task) = process(apiService.createTask(task)) suspend fun createTask(task: Task) = process(apiService.createTask(task))
fun hasAuthentication() = hostConfig.hasAuthentication()
} }

View file

@ -3,11 +3,14 @@ package com.habitrpg.wearos.habitica.data.repositories
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import com.habitrpg.common.habitica.models.tasks.TaskType import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.common.habitica.models.tasks.TasksOrder
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.models.tasks.TaskList import com.habitrpg.wearos.habitica.models.tasks.TaskList
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -19,25 +22,45 @@ class TaskLocalRepository @Inject constructor() {
TaskType.TODO to MutableLiveData<List<Task>>(), TaskType.TODO to MutableLiveData<List<Task>>(),
TaskType.REWARD to MutableLiveData<List<Task>>() TaskType.REWARD to MutableLiveData<List<Task>>()
) )
private val taskCountHelperValue = MutableLiveData<Long>()
fun getTasks(type: TaskType): Flow<List<Task>> { fun getTasks(type: TaskType): Flow<List<Task>> {
return tasks[type]?.asFlow() ?: emptyFlow() return tasks[type]?.asFlow() ?: emptyFlow()
} }
fun saveTasks(tasks: TaskList) { fun saveTasks(tasks: TaskList, order: TasksOrder?) {
val taskMap = mutableMapOf( val taskMap = mutableMapOf(
TaskType.HABIT to mutableListOf<Task>(), TaskType.HABIT to sortTasks(tasks.tasks, order?.habits ?: emptyList(), TaskType.HABIT),
TaskType.DAILY to mutableListOf<Task>(), TaskType.DAILY to sortTasks(tasks.tasks, order?.dailys ?: emptyList(), TaskType.DAILY),
TaskType.TODO to mutableListOf<Task>(), TaskType.TODO to sortTasks(tasks.tasks, order?.todos ?: emptyList(), TaskType.TODO),
TaskType.REWARD to mutableListOf<Task>() TaskType.REWARD to sortTasks(tasks.tasks, order?.rewards ?: emptyList(), TaskType.REWARD)
) )
for (task in tasks.tasks) {
if (task.value.type != null) {
taskMap[task.value.type]?.add(task.value)
}
}
for (type in taskMap) { for (type in taskMap) {
this.tasks[type.key]?.value = type.value this.tasks[type.key]?.value = type.value
} }
taskCountHelperValue.value = Date().time
}
private fun sortTasks(taskMap: MutableMap<String, Task>, taskOrder: List<String>, type: TaskType): List<Task> {
val taskList = ArrayList<Task>()
var position = 0
for (taskId in taskOrder) {
val task = taskMap[taskId]
if (task != null) {
task.position = position
taskList.add(task)
position++
taskMap.remove(taskId)
}
}
for (task in taskMap.values) {
if (task.type != type) continue
task.position = position
taskList.add(task)
position++
}
return taskList
} }
fun updateTask(task: Task) { fun updateTask(task: Task) {
@ -51,6 +74,7 @@ class TaskLocalRepository @Inject constructor() {
oldList?.let { oldList?.let {
tasks[task.type]?.value = it tasks[task.type]?.value = it
} }
taskCountHelperValue.value = Date().time
} }
fun getTask(taskID: String): Flow<Task?> { fun getTask(taskID: String): Flow<Task?> {
@ -63,10 +87,23 @@ class TaskLocalRepository @Inject constructor() {
return emptyFlow() return emptyFlow()
} }
fun getTaskCounts() = flowOf(mapOf( fun getTaskCounts() = taskCountHelperValue.asFlow().map {
mapOf(
TaskType.HABIT.value to (tasks[TaskType.HABIT]?.value?.size ?: 0), TaskType.HABIT.value to (tasks[TaskType.HABIT]?.value?.size ?: 0),
TaskType.DAILY.value to (tasks[TaskType.DAILY]?.value?.size ?: 0), TaskType.DAILY.value to (tasks[TaskType.DAILY]?.value?.size ?: 0),
TaskType.TODO.value to (tasks[TaskType.TODO]?.value?.size ?: 0), TaskType.TODO.value to (tasks[TaskType.TODO]?.value?.size ?: 0),
TaskType.REWARD.value to (tasks[TaskType.REWARD]?.value?.size ?: 0), TaskType.REWARD.value to (tasks[TaskType.REWARD]?.value?.size ?: 0),
)) )
}
fun getActiveTaskCounts() = taskCountHelperValue.asFlow().map {
mapOf(
TaskType.HABIT.value to (tasks[TaskType.HABIT]?.value?.size ?: 0),
TaskType.DAILY.value to (tasks[TaskType.DAILY]?.value?.filter { it.isDue == true && !it.completed }?.size
?: 0),
TaskType.TODO.value to (tasks[TaskType.TODO]?.value?.filter { !it.completed }?.size
?: 0),
TaskType.REWARD.value to (tasks[TaskType.REWARD]?.value?.size ?: 0),
)
}
} }

View file

@ -3,6 +3,7 @@ package com.habitrpg.wearos.habitica.data.repositories
import com.habitrpg.common.habitica.models.responses.TaskDirection import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskScoringResult import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import com.habitrpg.common.habitica.models.tasks.TaskType import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.common.habitica.models.tasks.TasksOrder
import com.habitrpg.wearos.habitica.data.ApiClient import com.habitrpg.wearos.habitica.data.ApiClient
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.models.tasks.TaskList import com.habitrpg.wearos.habitica.models.tasks.TaskList
@ -11,11 +12,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import javax.inject.Inject import javax.inject.Inject
class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepository: TaskLocalRepository) { class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepository: TaskLocalRepository, val userLocalRepository: UserLocalRepository) {
suspend fun retrieveTasks(): TaskList? { suspend fun retrieveTasks(order: TasksOrder?): TaskList? {
val tasks = apiClient.getTasks() val tasks = apiClient.getTasks()
tasks?.let { localRepository.saveTasks(tasks) } tasks?.let { localRepository.saveTasks(tasks, order) }
return tasks return tasks
} }
fun getTasks(taskType: TaskType) = localRepository.getTasks(taskType) fun getTasks(taskType: TaskType) = localRepository.getTasks(taskType)
@ -25,9 +26,36 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
val result = apiClient.scoreTask(id, direction.text) val result = apiClient.scoreTask(id, direction.text)
if (result != null) { if (result != null) {
task.completed = direction == TaskDirection.UP task.completed = direction == TaskDirection.UP
task.value += result.delta
if (task.type == TaskType.HABIT) {
if (direction == TaskDirection.UP) {
task.counterUp = task.counterUp?.plus(1) ?: 1
} else {
task.counterUp = task.counterDown?.plus(1) ?: 1
}
} else if (task.type == TaskType.DAILY) {
if (direction == TaskDirection.UP) {
task.streak = task.streak?.plus(1) ?: 1
} else {
task.streak = task.streak?.minus(1) ?: 0
}
}
localRepository.updateTask(task) localRepository.updateTask(task)
} }
return result?.let { TaskScoringResult(it, user?.stats) } val scoringResult = result?.let { TaskScoringResult(it, user?.stats) }
if (user != null) {
user.stats?.hp = result?.hp
user.stats?.exp = result?.exp
user.stats?.mp = result?.mp
user.stats?.gp = result?.gp
user.stats?.lvl = result?.lvl
/*user?.party?.quest?.progress?.up = (
user?.party?.quest?.progress?.up
?: 0F
) + (result?._tmp?.quest?.progressDelta?.toFloat() ?: 0F)*/
userLocalRepository.saveUser(user)
}
return scoringResult
} }
fun getTask(taskID: String?): Flow<Task?> { fun getTask(taskID: String?): Flow<Task?> {
@ -43,4 +71,5 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
} }
fun getTaskCounts() = localRepository.getTaskCounts() fun getTaskCounts() = localRepository.getTaskCounts()
fun getActiveTaskCounts() = localRepository.getActiveTaskCounts()
} }

View file

@ -5,7 +5,8 @@ import com.habitrpg.wearos.habitica.models.user.User
import javax.inject.Inject import javax.inject.Inject
class UserRepository @Inject constructor(val apiClient: ApiClient, val localRepository: UserLocalRepository) { class UserRepository @Inject constructor(val apiClient: ApiClient, val localRepository: UserLocalRepository) {
val hasAuthentication: Boolean
get() = apiClient.hasAuthentication()
fun getUser() = localRepository.getUser() fun getUser() = localRepository.getUser()
suspend fun retrieveUser(): User? { suspend fun retrieveUser(): User? {
@ -21,5 +22,8 @@ class UserRepository @Inject constructor(val apiClient: ApiClient, val localRepo
suspend fun sleep() = apiClient.sleep() suspend fun sleep() = apiClient.sleep()
suspend fun revive() = apiClient.revive() suspend fun revive() = apiClient.revive()
suspend fun runCron() = apiClient.runCron() suspend fun runCron() {
apiClient.runCron()
retrieveUser()
}
} }

View file

@ -92,6 +92,21 @@ open class Task constructor(): Parcelable {
val completedChecklistCount: Int val completedChecklistCount: Int
get() = checklist?.count { it.completed } ?: 0 get() = checklist?.count { it.completed } ?: 0
val streakString: String?
get() {
return if (counterUp != null && (counterUp ?: 0) > 0 && counterDown != null && (counterDown ?: 0) > 0) {
"+" + counterUp.toString() + " | -" + counterDown?.toString()
} else if (counterUp != null && (counterUp ?: 0) > 0) {
"+" + counterUp.toString()
} else if (counterDown != null && (counterDown ?: 0) > 0) {
"-" + counterDown.toString()
} else if ((streak ?: 0) > 0) {
return streak.toString()
} else {
null
}
}
val extraLightTaskColor: Int val extraLightTaskColor: Int
get() { get() {
return when { return when {

View file

@ -9,6 +9,7 @@ data class MenuItem(
val color: Int, val color: Int,
val textColor: Int, val textColor: Int,
val isProminent: Boolean = false, val isProminent: Boolean = false,
val isHidden: Boolean = false,
var detailText: String? = null, var detailText: String? = null,
val onClick: () -> Unit val onClick: () -> Unit
) )

View file

@ -1,10 +1,12 @@
package com.habitrpg.wearos.habitica.models.user package com.habitrpg.wearos.habitica.models.user
import com.habitrpg.common.habitica.models.Avatar import com.habitrpg.common.habitica.models.Avatar
import com.habitrpg.common.habitica.models.tasks.TasksOrder
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
class User: Avatar { class User: Avatar {
val tasksOrder: TasksOrder? = null
val isDead: Boolean val isDead: Boolean
get() = (stats?.hp ?: 0.0) <= 0.0 get() = (stats?.hp ?: 0.0) <= 0.0
override val currentMount: String? override val currentMount: String?

View file

@ -43,15 +43,6 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
) { ) {
openTaskFormActivity() openTaskFormActivity()
}, },
MenuItem(
TaskType.HABIT.value,
getString(R.string.habits),
AppCompatResources.getDrawable(this, R.drawable.icon_habits),
ContextCompat.getColor(this, R.color.watch_purple_200),
ContextCompat.getColor(this, R.color.watch_purple_700)
) {
openTasklist(TaskType.HABIT)
},
MenuItem( MenuItem(
TaskType.DAILY.value, TaskType.DAILY.value,
getString(R.string.dailies), getString(R.string.dailies),
@ -70,17 +61,27 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
) { ) {
openTasklist(TaskType.TODO) openTasklist(TaskType.TODO)
}, },
MenuItem(
TaskType.HABIT.value,
getString(R.string.habits),
AppCompatResources.getDrawable(this, R.drawable.icon_habits),
ContextCompat.getColor(this, R.color.watch_purple_200),
ContextCompat.getColor(this, R.color.watch_purple_700)
) {
openTasklist(TaskType.HABIT)
},
MenuItem( MenuItem(
TaskType.REWARD.value, TaskType.REWARD.value,
getString(R.string.rewards), getString(R.string.rewards),
AppCompatResources.getDrawable(this, R.drawable.icon_rewards), AppCompatResources.getDrawable(this, R.drawable.icon_rewards),
ContextCompat.getColor(this, R.color.watch_purple_200), ContextCompat.getColor(this, R.color.watch_purple_200),
ContextCompat.getColor(this, R.color.watch_purple_700) ContextCompat.getColor(this, R.color.watch_purple_700),
isHidden = true
) { ) {
openTasklist(TaskType.REWARD) openTasklist(TaskType.REWARD)
}, },
MenuItem( MenuItem(
"Stats", "stats",
getString(R.string.stats), getString(R.string.stats),
AppCompatResources.getDrawable(this, R.drawable.icon_stats), AppCompatResources.getDrawable(this, R.drawable.icon_stats),
ContextCompat.getColor(this, R.color.watch_purple_200), ContextCompat.getColor(this, R.color.watch_purple_200),
@ -110,13 +111,18 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
viewModel.user.observe(this) { viewModel.user.observe(this) {
adapter.title = it.profile?.name ?: "" adapter.title = it.profile?.name ?: ""
adapter.notifyItemChanged(0) adapter.notifyItemChanged(0)
val index = adapter.data.indexOfFirst { it.identifier == "stats" }
adapter.data[index].detailText = getString(R.string.user_level, it.stats?.lvl ?: 0)
adapter.notifyItemChanged(index+1)
} }
viewModel.taskCounts.observe(this) { viewModel.taskCounts.observe(this) {
adapter.data.forEach { menuItem -> adapter.data.forEach { menuItem ->
if (it.containsKey(menuItem.identifier) && it[menuItem.identifier]!! > 0) { if (it.containsKey(menuItem.identifier)) {
menuItem.detailText = it[menuItem.identifier].toString() if (it[menuItem.identifier]!! > 0) {
} else { menuItem.detailText = it[menuItem.identifier].toString()
menuItem.detailText = null } else {
menuItem.detailText = null
}
} }
} }
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()

View file

@ -69,17 +69,10 @@ class SettingsActivity: BaseActivity<ActivitySettingsBinding, SettingsViewModel>
adapter.data[index].value = viewModel.isTaskResultHidden() adapter.data[index].value = viewModel.isTaskResultHidden()
adapter.notifyItemChanged(index) adapter.notifyItemChanged(index)
}, },
SettingsItem(
"spacer",
getString(R.string.settings),
SettingsItem.Types.SPACER,
null
) {
},
SettingsItem( SettingsItem(
"logout", "logout",
getString(R.string.logout), getString(R.string.logout),
SettingsItem.Types.DESTRUCTIVE_BUTTON, SettingsItem.Types.BUTTON,
null null
) { ) {
showLogoutConfirmation() showLogoutConfirmation()

View file

@ -2,14 +2,16 @@ package com.habitrpg.wearos.habitica.ui.activities
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTaskDetailBinding import com.habitrpg.android.habitica.databinding.ActivityTaskDetailBinding
import com.habitrpg.wearos.habitica.ui.viewmodels.TaskDetailViewModel import com.habitrpg.wearos.habitica.ui.viewmodels.TaskDetailViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.Locale import java.util.Locale
@AndroidEntryPoint @AndroidEntryPoint
class TaskDetailActivity: BaseActivity<ActivityTaskDetailBinding, TaskDetailViewModel>() { class TaskDetailActivity : BaseActivity<ActivityTaskDetailBinding, TaskDetailViewModel>() {
override val viewModel: TaskDetailViewModel by viewModels() override val viewModel: TaskDetailViewModel by viewModels()
@ -29,15 +31,21 @@ class TaskDetailActivity: BaseActivity<ActivityTaskDetailBinding, TaskDetailView
} }
private fun subscribeUI() { private fun subscribeUI() {
viewModel.task.observe(this) { viewModel.task.observe(this) { task ->
binding.taskTypeView.text = it?.type?.value?.replaceFirstChar { binding.taskTypeView.text = task?.type?.value?.replaceFirstChar {
if (it.isLowerCase()) it.titlecase( if (it.isLowerCase()) it.titlecase(
Locale.getDefault() Locale.getDefault()
) else it.toString() ) else it.toString()
} }
binding.taskTextView.text = it?.text binding.taskTypeView.setTextColor(
if (it?.notes?.isNotBlank() == true) { ContextCompat.getColor(
binding.taskNotesView.text = it.notes this,
task?.extraLightTaskColor ?: R.color.white
)
)
binding.taskTextView.text = task?.text
if (task?.notes?.isNotBlank() == true) {
binding.taskNotesView.text = task.notes
binding.taskNotesView.isVisible = true binding.taskNotesView.isVisible = true
} else { } else {
binding.taskNotesView.isVisible = false binding.taskNotesView.isVisible = false

View file

@ -1,13 +1,16 @@
package com.habitrpg.wearos.habitica.ui.activities package com.habitrpg.wearos.habitica.ui.activities
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.TextView import android.widget.TextView
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
@ -25,7 +28,15 @@ class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel
updateTaskTypeButton(binding.todoButton, TaskType.TODO) updateTaskTypeButton(binding.todoButton, TaskType.TODO)
updateTaskTypeButton(binding.dailyButton, TaskType.DAILY) updateTaskTypeButton(binding.dailyButton, TaskType.DAILY)
updateTaskTypeButton(binding.habitButton, TaskType.HABIT) updateTaskTypeButton(binding.habitButton, TaskType.HABIT)
binding.confirmationTitle.text = getString(R.string.create_task, value?.value) val typeName = getString(when(value) {
TaskType.HABIT -> R.string.habit
TaskType.DAILY -> R.string.daily
TaskType.TODO -> R.string.todo
TaskType.REWARD -> R.string.reward
else -> R.string.task
})
binding.confirmationTitle.text = getString(R.string.new_task_x, typeName)
binding.saveButton.text = getString(R.string.save_task_x, typeName)
} }
override val viewModel: TaskFormViewModel by viewModels() override val viewModel: TaskFormViewModel by viewModels()
@ -72,13 +83,24 @@ class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel
binding.taskTypeHeader.isVisible = false binding.taskTypeHeader.isVisible = false
binding.taskTypeWrapper.isVisible = false binding.taskTypeWrapper.isVisible = false
binding.header.textView.text = getString(R.string.create_task, taskType?.value) binding.header.textView.text = getString(R.string.create_task, taskType?.value)
binding.confirmationTitle.text = getString(R.string.create_task, taskType?.value) binding.editText.requestFocus()
} else { } else {
taskType = TaskType.TODO taskType = TaskType.TODO
binding.header.textView.text = getString(R.string.create_task_title) binding.header.textView.text = getString(R.string.create_task_title)
} }
} }
override fun onResume() {
super.onResume()
if (binding.editText.hasFocus()) {
binding.editText.postDelayed(100) {
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.editText, InputMethodManager.SHOW_FORCED)
}
}
}
private fun updateTaskTypeButton(button: TextView, thisType: TaskType) { private fun updateTaskTypeButton(button: TextView, thisType: TaskType) {
if (taskType == thisType) { if (taskType == thisType) {
button.backgroundTintList = button.backgroundTintList =

View file

@ -6,6 +6,7 @@ import androidx.activity.viewModels
import androidx.wear.widget.WearableLinearLayoutManager import androidx.wear.widget.WearableLinearLayoutManager
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTasklistBinding import com.habitrpg.android.habitica.databinding.ActivityTasklistBinding
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.models.responses.TaskDirection import com.habitrpg.common.habitica.models.responses.TaskDirection
import com.habitrpg.common.habitica.models.responses.TaskScoringResult import com.habitrpg.common.habitica.models.responses.TaskScoringResult
import com.habitrpg.common.habitica.models.tasks.TaskType import com.habitrpg.common.habitica.models.tasks.TaskType
@ -32,6 +33,18 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
layoutManager = layoutManager =
WearableLinearLayoutManager(this@TaskListActivity, HabiticaScrollingLayoutCallback()) WearableLinearLayoutManager(this@TaskListActivity, HabiticaScrollingLayoutCallback())
adapter = this@TaskListActivity.adapter adapter = this@TaskListActivity.adapter
emptyItem = EmptyItem(
getString(R.string.no_tasks, getString(when(viewModel.taskType) {
TaskType.HABIT -> R.string.habit
TaskType.DAILY -> R.string.daily
TaskType.TODO -> R.string.todo
TaskType.REWARD -> R.string.reward
else -> R.string.task
}))
)
onRefresh = {
viewModel.retrieveTasks()
}
} }
viewModel.tasks.observe(this) { viewModel.tasks.observe(this) {
@ -46,10 +59,6 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
openTaskDetailActivity(it) openTaskDetailActivity(it)
} }
viewModel.user.observe(this) {
}
binding.addTaskButton.setOnClickListener { openTaskFormActivity() } binding.addTaskButton.setOnClickListener { openTaskFormActivity() }
} }

View file

@ -12,6 +12,7 @@ import androidx.preference.PreferenceManager
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTaskResultBinding import com.habitrpg.android.habitica.databinding.ActivityTaskResultBinding
import com.habitrpg.android.habitica.databinding.TaskRewardDropBinding import com.habitrpg.android.habitica.databinding.TaskRewardDropBinding
import com.habitrpg.android.habitica.extensions.localizedCapitalize
import com.habitrpg.common.habitica.extensions.dpToPx import com.habitrpg.common.habitica.extensions.dpToPx
import com.habitrpg.common.habitica.extensions.loadImage import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.models.responses.TaskScoringResult import com.habitrpg.common.habitica.models.responses.TaskScoringResult
@ -155,14 +156,16 @@ class TaskResultActivity : BaseActivity<ActivityTaskResultBinding, TaskResultVie
} }
} }
if (viewModel.result?.drop?.key != null) { if (viewModel.result?.drop?.key != null) {
elements.add(getString(R.string.some_x, viewModel.result?.drop?.type)) val type = viewModel.result?.drop?.type
dropBinding.imageView.loadImage(viewModel.result?.drop?.key) val key = viewModel.result?.drop?.key
elements.add(getString(R.string.some_x, type))
dropBinding.imageView.loadImage("Pet_" + type + "_" + key)
} }
dropBinding.textView.text = when (elements.size) { dropBinding.textView.text = when (elements.size) {
1 -> elements[0] 1 -> elements[0]
2 -> getString(R.string.x_and_y, elements[0], elements[1]) 2 -> getString(R.string.x_and_y, elements[0], elements[1])
else -> elements.joinToString(", ") else -> elements.joinToString(", ")
} }.localizedCapitalize()
} }
} }

View file

@ -2,9 +2,9 @@ package com.habitrpg.wearos.habitica.ui.adapters
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.android.habitica.databinding.RowHeaderBinding import com.habitrpg.android.habitica.databinding.RowHeaderBinding
import com.habitrpg.android.habitica.databinding.RowHubBinding import com.habitrpg.android.habitica.databinding.RowHubBinding
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.wearos.habitica.models.user.MenuItem import com.habitrpg.wearos.habitica.models.user.MenuItem
import com.habitrpg.wearos.habitica.ui.viewHolders.HeaderViewHolder import com.habitrpg.wearos.habitica.ui.viewHolders.HeaderViewHolder
import com.habitrpg.wearos.habitica.ui.viewHolders.HubViewHolder import com.habitrpg.wearos.habitica.ui.viewHolders.HubViewHolder
@ -27,19 +27,14 @@ class HubAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is HubViewHolder) { if (holder is HubViewHolder) {
val item = data[position - 1] holder.bind(getItemAt(position - 1))
holder.bind(item)
} else if (holder is HeaderViewHolder){ } else if (holder is HeaderViewHolder){
holder.bind(title) holder.bind(title)
} }
} }
override fun getItemViewType(position: Int): Int { private fun getItemAt(position: Int) = data.filter { !it.isHidden }[position]
return if (position == 0) 0 else 1 override fun getItemViewType(position: Int) = if (position == 0) 0 else 1
} override fun getItemCount() = data.filter { !it.isHidden }.size + 1
override fun getItemCount(): Int {
return data.size + 1
}
} }

View file

@ -90,7 +90,7 @@ class SettingsViewHolder(itemView: View) : BindableViewHolder<SettingsItem>(item
if (data.value as? Boolean == true) { if (data.value as? Boolean == true) {
binding.row.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.watch_purple_100)) binding.row.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.watch_purple_100))
binding.row.background.alpha = 127 binding.row.background.alpha = 102
} else { } else {
binding.row.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.watch_purple_5)) binding.row.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(itemView.context, R.color.watch_purple_5))
binding.row.background.alpha = 255 binding.row.background.alpha = 255

View file

@ -19,9 +19,11 @@ class HubViewHolder(itemView: View): BindableViewHolder<MenuItem>(itemView) {
if (data.isProminent) { if (data.isProminent) {
binding.iconView.setColorFilter(ContextCompat.getColor(itemView.context, R.color.black)) binding.iconView.setColorFilter(ContextCompat.getColor(itemView.context, R.color.black))
binding.rowContainer.backgroundTintList = ColorStateList.valueOf(data.color) binding.rowContainer.backgroundTintList = ColorStateList.valueOf(data.color)
binding.detailView.setTextColor(data.textColor)
} else { } else {
binding.iconView.setColorFilter(data.color) binding.iconView.setColorFilter(data.color)
binding.rowContainer.backgroundTintList = ContextCompat.getColorStateList(itemView.context, R.color.surface) binding.rowContainer.backgroundTintList = ContextCompat.getColorStateList(itemView.context, R.color.surface)
binding.detailView.setTextColor(ContextCompat.getColor(itemView.context, R.color.watch_purple_200))
} }
binding.root.setOnClickListener { binding.root.setOnClickListener {
data.onClick() data.onClick()

View file

@ -3,12 +3,12 @@ package com.habitrpg.wearos.habitica.ui.viewHolders.tasks
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView
import com.habitrpg.android.habitica.databinding.RowDailyBinding import com.habitrpg.android.habitica.databinding.RowDailyBinding
import com.habitrpg.wearos.habitica.ui.views.TaskTextView
class DailyViewHolder(itemView: View) : CheckedTaskViewHolder(itemView) { class DailyViewHolder(itemView: View) : CheckedTaskViewHolder(itemView) {
private val binding = RowDailyBinding.bind(itemView) private val binding = RowDailyBinding.bind(itemView)
override val titleView: TextView override val titleView: TaskTextView
get() = binding.title get() = binding.title
override val checkbox: ImageView override val checkbox: ImageView
get() = binding.checkbox get() = binding.checkbox

View file

@ -2,15 +2,15 @@ package com.habitrpg.wearos.habitica.ui.viewHolders.tasks
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.view.View import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.RowHabitBinding import com.habitrpg.android.habitica.databinding.RowHabitBinding
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.ui.views.TaskTextView
class HabitViewHolder(itemView: View) : TaskViewHolder(itemView) { class HabitViewHolder(itemView: View) : TaskViewHolder(itemView) {
private val binding = RowHabitBinding.bind(itemView) private val binding = RowHabitBinding.bind(itemView)
override val titleView: TextView override val titleView: TaskTextView
get() = binding.title get() = binding.title
init { init {

View file

@ -4,10 +4,11 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import com.habitrpg.android.habitica.databinding.RowRewardBinding import com.habitrpg.android.habitica.databinding.RowRewardBinding
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.ui.views.TaskTextView
class RewardViewHolder(itemView: View) : TaskViewHolder(itemView) { class RewardViewHolder(itemView: View) : TaskViewHolder(itemView) {
private val binding = RowRewardBinding.bind(itemView) private val binding = RowRewardBinding.bind(itemView)
override val titleView: TextView override val titleView: TaskTextView
get() = binding.title get() = binding.title
override fun bind(data: Task) { override fun bind(data: Task) {

View file

@ -1,13 +1,13 @@
package com.habitrpg.wearos.habitica.ui.viewHolders.tasks package com.habitrpg.wearos.habitica.ui.viewHolders.tasks
import android.view.View import android.view.View
import android.widget.TextView
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.ui.viewHolders.BindableViewHolder import com.habitrpg.wearos.habitica.ui.viewHolders.BindableViewHolder
import com.habitrpg.wearos.habitica.ui.views.TaskTextView
abstract class TaskViewHolder(itemView: View) : BindableViewHolder<Task>(itemView) { abstract class TaskViewHolder(itemView: View) : BindableViewHolder<Task>(itemView) {
var onTaskScore: (() -> Unit)? = null var onTaskScore: (() -> Unit)? = null
abstract val titleView: TextView abstract val titleView: TaskTextView
override fun bind(data: Task) { override fun bind(data: Task) {
titleView.text = data.text titleView.text = data.text
} }

View file

@ -5,10 +5,11 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.habitrpg.android.habitica.databinding.RowTodoBinding import com.habitrpg.android.habitica.databinding.RowTodoBinding
import com.habitrpg.wearos.habitica.ui.views.TaskTextView
class ToDoViewHolder(itemView: View) : CheckedTaskViewHolder(itemView) { class ToDoViewHolder(itemView: View) : CheckedTaskViewHolder(itemView) {
private val binding = RowTodoBinding.bind(itemView) private val binding = RowTodoBinding.bind(itemView)
override val titleView: TextView override val titleView: TaskTextView
get() = binding.title get() = binding.title
override val checkbox: ImageView override val checkbox: ImageView
get() = binding.checkbox get() = binding.checkbox

View file

@ -1,13 +1,11 @@
package com.habitrpg.wearos.habitica.ui.viewmodels package com.habitrpg.wearos.habitica.ui.viewmodels
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import com.habitrpg.wearos.habitica.data.repositories.TaskRepository import com.habitrpg.wearos.habitica.data.repositories.TaskRepository
import com.habitrpg.wearos.habitica.data.repositories.UserRepository import com.habitrpg.wearos.habitica.data.repositories.UserRepository
import com.habitrpg.wearos.habitica.managers.LoadingManager import com.habitrpg.wearos.habitica.managers.LoadingManager
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
@ -16,15 +14,6 @@ class MainViewModel @Inject constructor(
private val taskRepository: TaskRepository, private val taskRepository: TaskRepository,
exceptionBuilder: ExceptionHandlerBuilder, loadingManager: LoadingManager exceptionBuilder: ExceptionHandlerBuilder, loadingManager: LoadingManager
) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) { ) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) {
val taskCounts = taskRepository.getTaskCounts().asLiveData() val taskCounts = taskRepository.getActiveTaskCounts().asLiveData()
val user = userRepository.getUser().asLiveData() val user = userRepository.getUser().asLiveData()
init {
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
loadingManager.startLoading()
userRepository.retrieveUser()
taskRepository.retrieveTasks()
loadingManager.endLoading()
}
}
} }

View file

@ -31,8 +31,8 @@ class SettingsViewModel @Inject constructor(userRepository: UserRepository,
fun resyncData() { fun resyncData() {
viewModelScope.launch(exceptionBuilder.userFacing(this)) { viewModelScope.launch(exceptionBuilder.userFacing(this)) {
loadingManager.startLoading() loadingManager.startLoading()
userRepository.retrieveUser() val user = userRepository.retrieveUser()
taskRepository.retrieveTasks() taskRepository.retrieveTasks(user?.tasksOrder)
loadingManager.endLoading() loadingManager.endLoading()
} }
} }

View file

@ -5,7 +5,6 @@ import androidx.core.content.edit
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.android.gms.wearable.MessageClient import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.MessageEvent import com.google.android.gms.wearable.MessageEvent
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.helpers.KeyHelper import com.habitrpg.common.habitica.helpers.KeyHelper
import com.habitrpg.wearos.habitica.data.ApiClient import com.habitrpg.wearos.habitica.data.ApiClient
import com.habitrpg.wearos.habitica.data.repositories.UserRepository import com.habitrpg.wearos.habitica.data.repositories.UserRepository
@ -18,7 +17,6 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class SplashViewModel @Inject constructor(userRepository: UserRepository, class SplashViewModel @Inject constructor(userRepository: UserRepository,
exceptionBuilder: ExceptionHandlerBuilder, exceptionBuilder: ExceptionHandlerBuilder,
val hostConfig: HostConfig,
val apiClient: ApiClient, val apiClient: ApiClient,
val sharedPreferences: SharedPreferences, val sharedPreferences: SharedPreferences,
val keyHelper: KeyHelper?, loadingManager: LoadingManager val keyHelper: KeyHelper?, loadingManager: LoadingManager
@ -26,7 +24,7 @@ class SplashViewModel @Inject constructor(userRepository: UserRepository,
lateinit var onLoginCompleted: (Boolean) -> Unit lateinit var onLoginCompleted: (Boolean) -> Unit
val hasAuthentication: Boolean val hasAuthentication: Boolean
get() { get() {
return hostConfig.hasAuthentication() return apiClient.hasAuthentication()
} }
override fun onMessageReceived(event: MessageEvent) { override fun onMessageReceived(event: MessageEvent) {

View file

@ -12,6 +12,7 @@ import com.habitrpg.wearos.habitica.managers.LoadingManager
import com.habitrpg.wearos.habitica.models.tasks.Task import com.habitrpg.wearos.habitica.models.tasks.Task
import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder import com.habitrpg.wearos.habitica.util.ExceptionHandlerBuilder
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -24,14 +25,24 @@ class TaskListViewModel @Inject constructor(
) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) { ) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) {
val taskType = TaskType.from(savedStateHandle.get<String>("task_type")) val taskType = TaskType.from(savedStateHandle.get<String>("task_type"))
val tasks = taskRepository.getTasks(taskType ?: TaskType.HABIT).asLiveData() val tasks = taskRepository.getTasks(taskType ?: TaskType.HABIT).asLiveData()
val user = userRepository.getUser() val user = userRepository.getUser()
.asLiveData() .asLiveData()
fun scoreTask(task: Task, direction: TaskDirection, onResult: (TaskScoringResult?) -> Unit) { fun scoreTask(task: Task, direction: TaskDirection, onResult: (TaskScoringResult?) -> Unit) {
viewModelScope.launch(exceptionBuilder.userFacing(this)) { viewModelScope.launch(exceptionBuilder.userFacing(this)) {
val result = taskRepository.scoreTask(user.value, task, direction) val result = taskRepository.scoreTask(
userRepository.localRepository.getUser().first(),
task,
direction
)
onResult(result) onResult(result)
} }
} }
fun retrieveTasks() {
viewModelScope.launch(exceptionBuilder.userFacing(this)) {
val user = userRepository.retrieveUser()
taskRepository.retrieveTasks(user?.tasksOrder)
}
}
} }

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