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

View file

@ -150,7 +150,7 @@ android {
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'
versionCode 4000
versionCode app_version_code
versionName app_version_name
targetSdkVersion target_sdk
@ -212,22 +212,26 @@ android {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"staff\""
resValue "string", "app_name", "Habitica Staff"
versionCode app_version_code + 6
}
alpha {
dimension "buildType"
buildConfigField "String", "TESTING_LEVEL", "\"alpha\""
resValue "string", "app_name", "Habitica Alpha"
versionCode app_version_code + 4
}
beta {
buildConfigField "String", "TESTING_LEVEL", "\"beta\""
dimension "buildType"
versionCode app_version_code + 2
}
prod {
buildConfigField "String", "TESTING_LEVEL", "\"production\""
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_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_compact">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; 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="user_level">Lvl %d</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="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="caused_damage">You caused damage to the boss</string>
<string name="avatar_style">Style</string>
<string name="failed">Failed to load</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_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>

View file

@ -69,7 +69,7 @@ class ChallengeRepositoryImpl(
for ((key, value) in stringListMap) {
val taskIdList = value.map { t -> t.id ?: "" }
if (key == null) continue
when (key) {
TaskType.HABIT -> tasksOrder.habits = 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.executors.PostExecutionThread
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.common.habitica.models.responses.TaskScoringResult
import io.reactivex.rxjava3.core.Flowable
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
class DisplayItemDropUseCase @Inject
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 snackbarText = StringBuilder(data?.drop?.dialog ?: "")
if (data?.questItemsFound ?: 0 > 0 && requestValues.showQuestItems) {
if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) {
if (snackbarText.isNotEmpty())
snackbarText.append('\n')
snackbarText.append(requestValues.context.getString(R.string.quest_items_found, data!!.questItemsFound))
}
if (snackbarText.isNotEmpty()) {
GlobalScope.launch(context = Dispatchers.Main) {
MainScope().launch(context = Dispatchers.Main) {
delay(3000L)
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,

View file

@ -1,11 +1,11 @@
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.common.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.models.user.Stats
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.pow
import kotlin.math.roundToLong
@ -46,6 +46,9 @@ class ScoreTaskLocallyInteractor {
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? {
return if (task.type == TaskType.HABIT || direction == TaskDirection.UP) {
val stats = user.stats ?: return null
@ -67,14 +70,16 @@ class ScoreTaskLocallyInteractor {
TaskType.HABIT -> scoreHabit(user, task, direction)
TaskType.DAILY -> scoreDaily(user, task, direction)
TaskType.TODO -> scoreToDo(user, task, direction)
TaskType.REWARD -> scoreReward(user, task, direction)
else -> {}
}
if (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.lvl = user.stats?.lvl ?: 0 + 1
result.lvl = (user.stats?.lvl ?: 0) + 1
result.hp = 50.0
} else {
result.lvl = user.stats?.lvl ?: 0

View file

@ -29,7 +29,6 @@ import java.util.Date
import java.util.GregorianCalendar
open class Task : RealmObject, BaseMainObject, Parcelable {
override val realmClass: Class<Task>
get() = Task::class.java
override val primaryIdentifier: String?
@ -109,6 +108,21 @@ open class Task : RealmObject, BaseMainObject, Parcelable {
val completedChecklistCount: Int
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
get() {
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.ui.AvatarWithBarsViewModel
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.SnackbarDisplayType
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog

View file

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

View file

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

View file

@ -8,7 +8,6 @@ import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
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.Food
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.User
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.BottomSheetMenuItem
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.Flowable
import io.reactivex.rxjava3.subjects.PublishSubject
@ -105,7 +105,7 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
hatchingItem?.key + "-" + item?.key
}
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 {
@ -179,9 +179,9 @@ class ItemRecyclerAdapter(val context: Context) : BaseRecyclerViewAdapter<OwnedI
}
} else if (item is 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)))
} else if (ownedItem?.numberOwned ?: 0 > 0) {
} else if ((ownedItem?.numberOwned ?: 0) > 0) {
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.ui.adapter.inventory.EquipmentRecyclerViewAdapter
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 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.adapter.inventory.ItemRecyclerAdapter
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.common.habitica.extensions.loadImage
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.adapter.inventory.ItemRecyclerAdapter
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.common.habitica.extensions.loadImage
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.ui.adapter.inventory.ShopRecyclerAdapter
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.viewmodels.MainUserViewModel
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.ui.adapter.inventory.StableRecyclerAdapter
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.SafeDefaultItemAnimator
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.social.Challenge
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.FullProfileActivity
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.HabitViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.RewardViewHolder
import com.habitrpg.android.habitica.ui.viewHolders.tasks.TodoViewHolder
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 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 javax.inject.Inject
class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>() {
@ -117,7 +117,8 @@ class ChallengeDetailFragment : BaseMainFragment<FragmentChallengeDetailBinding>
val rewards = ArrayList<Task>()
for (entry in taskList) {
when (entry.type) {
val type = entry.type ?: continue
when (type) {
TaskType.TODO -> todos.add(entry)
TaskType.HABIT -> habits.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.ui.adapter.social.ChallengesListViewAdapter
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 io.reactivex.rxjava3.core.Flowable
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.ui.adapter.social.GuildListAdapter
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.SafeDefaultItemAnimator
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.RxErrorHandler
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.common.habitica.models.tasks.TaskType
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.TaskFormActivity
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.TodosRecyclerViewAdapter
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.viewHolders.tasks.BaseTaskViewHolder
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.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.disposables.CompositeDisposable
import kotlinx.coroutines.Job
@ -471,6 +471,7 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
viewModel?.setActiveFilter(TaskType.DAILY, Task.FILTER_ACTIVE)
}
}
else -> {}
}
}
}
@ -534,6 +535,10 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
fragment.tutorialStepIdentifier = "todos"
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.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 com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.EmptyItemBinding
import com.habitrpg.android.habitica.databinding.FailedItemBinding
import com.habitrpg.android.habitica.extensions.inflate
import com.habitrpg.common.habitica.helpers.EmptyItem
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.RecyclerViewStateAdapter
data class EmptyItem(
var title: String,
var text: String? = null,
var iconResource: Int? = null,
var buttonLabel: String? = null,
var onButtonTap: (() -> Unit)? = null
)
class RecyclerViewEmptySupport @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : RecyclerView(context, attrs) {
var onRefresh: (() -> Unit)?
get() = emptyAdapter.onRefresh
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
set(value) {
field = value
@ -37,7 +21,7 @@ class RecyclerViewEmptySupport : RecyclerView {
RecyclerViewState.DISPLAYING_DATA -> updateAdapter(actualAdapter)
else -> {
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) {
field = value
emptyAdapter.notifyDataSetChanged()
emptyAdapter.emptyItem = value
}
private var actualAdapter: Adapter<*>? = null
private val emptyAdapter: Adapter<ViewHolder> = object : Adapter<ViewHolder>() {
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))
}
}
private val emptyAdapter = RecyclerViewStateAdapter()
override fun onBindViewHolder(holder: 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 -> 0
RecyclerViewState.FAILED -> 1
else -> 2
}
}
}
private val observer = object : RecyclerView.AdapterDataObserver() {
private val observer = object : AdapterDataObserver() {
override fun onChanged() {
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) {
if (actualAdapter != null && !isInitial) {
state = if (actualAdapter != null && !isInitial) {
val emptyViewVisible = actualAdapter?.itemCount == 0
if (emptyViewVisible) {
state = RecyclerViewState.EMPTY
RecyclerViewState.EMPTY
} else {
state = RecyclerViewState.DISPLAYING_DATA
RecyclerViewState.DISPLAYING_DATA
}
} else {
state = RecyclerViewState.LOADING
RecyclerViewState.LOADING
}
}
@ -135,38 +76,4 @@ class RecyclerViewEmptySupport : RecyclerView {
actualAdapter = adapter
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 androidx.core.content.ContextCompat
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.common.habitica.models.responses.TaskDirection
class HabitViewHolder(
itemView: View,
@ -110,15 +110,8 @@ class HabitViewHolder(
this.btnMinus.isClickable = false
}
var streakString = ""
if (data.counterUp != null && data.counterUp ?: 0 > 0 && data.counterDown != null && data.counterDown ?: 0 > 0) {
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()) {
val streakString = task?.streakString
if (streakString?.isNotEmpty() == true) {
streakTextView.text = streakString
streakTextView.visibility = View.VISIBLE
streakIconView.visibility = View.VISIBLE

View file

@ -4,7 +4,6 @@ import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.lifecycle.viewModelScope
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.components.UserComponent
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.models.TutorialStep
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.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.schedulers.Schedulers
import io.realm.kotlin.isValid
@ -136,7 +136,7 @@ class MainActivityViewModel : BaseViewModel(), TutorialView.OnTutorialReaction {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ maintenanceResponse ->
if (maintenanceResponse == null) {
if (maintenanceResponse.activeMaintenance == null) {
return@subscribe
}
onResult(maintenanceResponse)

View file

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

View file

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

View file

@ -4,9 +4,10 @@ buildscript {
ext {
target_sdk = 32
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'
appcompat_version = '1.4.2'
lifecycle_version = '2.4.1'
@ -37,7 +38,7 @@ buildscript {
classpath "io.realm:realm-gradle-plugin:10.10.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
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.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
import android.os.Parcel
import android.os.Parcelable
class TaskDirectionDataTemp {
var drop: TaskDirectionDataDrop? = null
@ -12,10 +15,37 @@ class TaskDirectionDataQuest {
var collection: Int = 0
}
class TaskDirectionDataDrop {
class TaskDirectionDataDrop() : Parcelable {
var value: Int = 0
var key: String? = null
var type: 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() {
hasLeveledUp = data.lvl > (stats?.lvl ?: 0)
healthDelta = data.hp - (stats?.hp ?: 0.0)
if (hasLeveledUp) {
experienceDelta = (stats?.toNextLevel ?: 0).toDouble() - (stats?.exp ?: 0.0) + data.exp
experienceDelta = if (hasLeveledUp) {
(stats?.toNextLevel ?: 0).toDouble() - (stats?.exp ?: 0.0) + data.exp
} else {
experienceDelta = data.exp - (stats?.exp ?: 0.0)
data.exp - (stats?.exp ?: 0.0)
}
manaDelta = data.mp - (stats?.mp ?: 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
questDamage = parcel.readValue(Double::class.java.classLoader) as? Double
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) {
@ -51,6 +52,7 @@ class TaskScoringResult(): Parcelable {
parcel.writeValue(level)
parcel.writeValue(questDamage)
parcel.writeValue(questItemsFound)
parcel.writeValue(drop)
}
override fun describeContents(): Int {

View file

@ -1,6 +1,15 @@
package com.habitrpg.common.habitica.models.tasks
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 dailys: 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.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 tierIconView = ImageView(context)
@ -54,7 +56,7 @@ class UsernameLabel(context: Context?, attrs: AttributeSet?) : LinearLayout(cont
textViewParams.gravity = Gravity.CENTER_VERTICAL
textViewParams.weight = 1.0f
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.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
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
android:id="@+id/emptyViewDescription"
style="@style/Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="14sp"
android:textColor="@color/text_ternary"
tools:text="No Items" />
@ -37,7 +37,6 @@
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/HabiticaButton.Primary"
android:visibility="gone"
tools:visibility="visible"
android:layout_marginTop="@dimen/spacing_large"/>

View file

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

View file

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

View file

@ -27,6 +27,7 @@
<string name="settings">Settings</string>
<string name="new_task">New Task</string>
<string name="avatar">Avatar</string>
<string name="user_level">Lvl. %d</string>
<string name="action_edit">Edit</string>
<string name="action_cancel">Cancel</string>
@ -54,6 +55,12 @@
<string name="create_task_title">Create a Task</string>
<string name="complete_task_title">Complete a Task</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="task">Task</string>
<string name="failed">Failed to load</string>
<string name="retry">Retry</string>
</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"
minSdk 26
targetSdk target_sdk
versionCode 4001
versionCode app_version_code + 1
versionName app_version_name
}
@ -79,6 +79,8 @@ dependencies {
implementation "com.google.dagger:hilt-android:$daggerhilt_version"
kapt "com.google.dagger:hilt-compiler:$daggerhilt_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
repositories {
mavenCentral()

View file

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.habitrpg.android.habitica">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<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.helpers.MarkdownParser
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.ui.activities.BaseActivity
import com.habitrpg.wearos.habitica.ui.activities.FaintActivity
import com.habitrpg.wearos.habitica.ui.activities.RYAActivity
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
@ -20,6 +22,7 @@ import javax.inject.Inject
class MainApplication : Application() {
@Inject lateinit var userRepository: UserRepository
@Inject lateinit var taskRepository: TaskRepository
override fun onCreate() {
super.onCreate()
@ -40,5 +43,13 @@ class MainApplication : Application() {
}
}.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
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import com.amplitude.api.Amplitude
import com.habitrpg.common.habitica.BuildConfig
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.tasks.Task
import okhttp3.Cache
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.logging.HttpLoggingInterceptor
@ -34,7 +37,21 @@ class ApiClient @Inject constructor(
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()
if (BuildConfig.DEBUG) {
logging.level = HttpLoggingInterceptor.Level.BODY
@ -44,15 +61,36 @@ class ApiClient @Inject constructor(
val calendar = GregorianCalendar()
val timeZone = calendar.timeZone
val timezoneOffset = -TimeUnit.MINUTES.convert(timeZone.getOffset(calendar.timeInMillis).toLong(), TimeUnit.MILLISECONDS)
val cacheSize: Long = 10 * 1024 * 1024 // 10 MB
val timezoneOffset = -TimeUnit.MINUTES.convert(
timeZone.getOffset(calendar.timeInMillis).toLong(),
TimeUnit.MILLISECONDS
)
val cacheSize = (5 * 1024 * 1024).toLong()
val cache = Cache(File(context.cacheDir, "http_cache"), cacheSize)
val client = OkHttpClient.Builder()
.cache(cache)
.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 ->
val original = chain.request()
var builder: Request.Builder = original.newBuilder()
@ -70,8 +108,16 @@ class ApiClient @Inject constructor(
builder = builder.header("Authorization", "Basic " + BuildConfig.STAGING_KEY)
}
val request = builder.method(original.method, original.body)
.removeHeader("Pragma")
.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)
.build()
@ -111,6 +157,9 @@ class ApiClient @Inject constructor(
suspend fun runCron() = process(apiService.runCron())
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))
fun hasAuthentication() = hostConfig.hasAuthentication()
}

View file

@ -3,11 +3,14 @@ package com.habitrpg.wearos.habitica.data.repositories
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
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.TaskList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import java.util.Date
import javax.inject.Inject
import javax.inject.Singleton
@ -19,25 +22,45 @@ class TaskLocalRepository @Inject constructor() {
TaskType.TODO to MutableLiveData<List<Task>>(),
TaskType.REWARD to MutableLiveData<List<Task>>()
)
private val taskCountHelperValue = MutableLiveData<Long>()
fun getTasks(type: TaskType): Flow<List<Task>> {
return tasks[type]?.asFlow() ?: emptyFlow()
}
fun saveTasks(tasks: TaskList) {
fun saveTasks(tasks: TaskList, order: TasksOrder?) {
val taskMap = mutableMapOf(
TaskType.HABIT to mutableListOf<Task>(),
TaskType.DAILY to mutableListOf<Task>(),
TaskType.TODO to mutableListOf<Task>(),
TaskType.REWARD to mutableListOf<Task>()
TaskType.HABIT to sortTasks(tasks.tasks, order?.habits ?: emptyList(), TaskType.HABIT),
TaskType.DAILY to sortTasks(tasks.tasks, order?.dailys ?: emptyList(), TaskType.DAILY),
TaskType.TODO to sortTasks(tasks.tasks, order?.todos ?: emptyList(), TaskType.TODO),
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) {
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) {
@ -51,6 +74,7 @@ class TaskLocalRepository @Inject constructor() {
oldList?.let {
tasks[task.type]?.value = it
}
taskCountHelperValue.value = Date().time
}
fun getTask(taskID: String): Flow<Task?> {
@ -63,10 +87,23 @@ class TaskLocalRepository @Inject constructor() {
return emptyFlow()
}
fun getTaskCounts() = flowOf(mapOf(
fun getTaskCounts() = taskCountHelperValue.asFlow().map {
mapOf(
TaskType.HABIT.value to (tasks[TaskType.HABIT]?.value?.size ?: 0),
TaskType.DAILY.value to (tasks[TaskType.DAILY]?.value?.size ?: 0),
TaskType.TODO.value to (tasks[TaskType.TODO]?.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.TaskScoringResult
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.models.tasks.Task
import com.habitrpg.wearos.habitica.models.tasks.TaskList
@ -11,11 +12,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
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()
tasks?.let { localRepository.saveTasks(tasks) }
tasks?.let { localRepository.saveTasks(tasks, order) }
return tasks
}
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)
if (result != null) {
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)
}
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?> {
@ -43,4 +71,5 @@ class TaskRepository @Inject constructor(val apiClient: ApiClient, val localRepo
}
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
class UserRepository @Inject constructor(val apiClient: ApiClient, val localRepository: UserLocalRepository) {
val hasAuthentication: Boolean
get() = apiClient.hasAuthentication()
fun getUser() = localRepository.getUser()
suspend fun retrieveUser(): User? {
@ -21,5 +22,8 @@ class UserRepository @Inject constructor(val apiClient: ApiClient, val localRepo
suspend fun sleep() = apiClient.sleep()
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
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
get() {
return when {

View file

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

View file

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

View file

@ -43,15 +43,6 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
) {
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(
TaskType.DAILY.value,
getString(R.string.dailies),
@ -70,17 +61,27 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
) {
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(
TaskType.REWARD.value,
getString(R.string.rewards),
AppCompatResources.getDrawable(this, R.drawable.icon_rewards),
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)
},
MenuItem(
"Stats",
"stats",
getString(R.string.stats),
AppCompatResources.getDrawable(this, R.drawable.icon_stats),
ContextCompat.getColor(this, R.color.watch_purple_200),
@ -110,13 +111,18 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>() {
viewModel.user.observe(this) {
adapter.title = it.profile?.name ?: ""
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) {
adapter.data.forEach { menuItem ->
if (it.containsKey(menuItem.identifier) && it[menuItem.identifier]!! > 0) {
menuItem.detailText = it[menuItem.identifier].toString()
} else {
menuItem.detailText = null
if (it.containsKey(menuItem.identifier)) {
if (it[menuItem.identifier]!! > 0) {
menuItem.detailText = it[menuItem.identifier].toString()
} else {
menuItem.detailText = null
}
}
}
adapter.notifyDataSetChanged()

View file

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

View file

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

View file

@ -1,13 +1,16 @@
package com.habitrpg.wearos.habitica.ui.activities
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
@ -25,7 +28,15 @@ class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel
updateTaskTypeButton(binding.todoButton, TaskType.TODO)
updateTaskTypeButton(binding.dailyButton, TaskType.DAILY)
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()
@ -72,13 +83,24 @@ class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel
binding.taskTypeHeader.isVisible = false
binding.taskTypeWrapper.isVisible = false
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 {
taskType = TaskType.TODO
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) {
if (taskType == thisType) {
button.backgroundTintList =

View file

@ -6,6 +6,7 @@ import androidx.activity.viewModels
import androidx.wear.widget.WearableLinearLayoutManager
import com.habitrpg.android.habitica.R
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.TaskScoringResult
import com.habitrpg.common.habitica.models.tasks.TaskType
@ -32,6 +33,18 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
layoutManager =
WearableLinearLayoutManager(this@TaskListActivity, HabiticaScrollingLayoutCallback())
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) {
@ -46,10 +59,6 @@ class TaskListActivity: BaseActivity<ActivityTasklistBinding, TaskListViewModel>
openTaskDetailActivity(it)
}
viewModel.user.observe(this) {
}
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.databinding.ActivityTaskResultBinding
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.loadImage
import com.habitrpg.common.habitica.models.responses.TaskScoringResult
@ -155,14 +156,16 @@ class TaskResultActivity : BaseActivity<ActivityTaskResultBinding, TaskResultVie
}
}
if (viewModel.result?.drop?.key != null) {
elements.add(getString(R.string.some_x, viewModel.result?.drop?.type))
dropBinding.imageView.loadImage(viewModel.result?.drop?.key)
val type = viewModel.result?.drop?.type
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) {
1 -> elements[0]
2 -> getString(R.string.x_and_y, elements[0], elements[1])
else -> elements.joinToString(", ")
}
}.localizedCapitalize()
}
}

View file

@ -2,9 +2,9 @@ package com.habitrpg.wearos.habitica.ui.adapters
import android.view.ViewGroup
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.RowHubBinding
import com.habitrpg.common.habitica.extensions.layoutInflater
import com.habitrpg.wearos.habitica.models.user.MenuItem
import com.habitrpg.wearos.habitica.ui.viewHolders.HeaderViewHolder
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) {
if (holder is HubViewHolder) {
val item = data[position - 1]
holder.bind(item)
holder.bind(getItemAt(position - 1))
} else if (holder is HeaderViewHolder){
holder.bind(title)
}
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) 0 else 1
}
override fun getItemCount(): Int {
return data.size + 1
}
private fun getItemAt(position: Int) = data.filter { !it.isHidden }[position]
override fun getItemViewType(position: Int) = if (position == 0) 0 else 1
override fun getItemCount() = data.filter { !it.isHidden }.size + 1
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,6 @@ import androidx.core.content.edit
import androidx.lifecycle.viewModelScope
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.MessageEvent
import com.habitrpg.common.habitica.api.HostConfig
import com.habitrpg.common.habitica.helpers.KeyHelper
import com.habitrpg.wearos.habitica.data.ApiClient
import com.habitrpg.wearos.habitica.data.repositories.UserRepository
@ -18,7 +17,6 @@ import javax.inject.Inject
@HiltViewModel
class SplashViewModel @Inject constructor(userRepository: UserRepository,
exceptionBuilder: ExceptionHandlerBuilder,
val hostConfig: HostConfig,
val apiClient: ApiClient,
val sharedPreferences: SharedPreferences,
val keyHelper: KeyHelper?, loadingManager: LoadingManager
@ -26,7 +24,7 @@ class SplashViewModel @Inject constructor(userRepository: UserRepository,
lateinit var onLoginCompleted: (Boolean) -> Unit
val hasAuthentication: Boolean
get() {
return hostConfig.hasAuthentication()
return apiClient.hasAuthentication()
}
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.util.ExceptionHandlerBuilder
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -24,14 +25,24 @@ class TaskListViewModel @Inject constructor(
) : BaseViewModel(userRepository, exceptionBuilder, loadingManager) {
val taskType = TaskType.from(savedStateHandle.get<String>("task_type"))
val tasks = taskRepository.getTasks(taskType ?: TaskType.HABIT).asLiveData()
val user = userRepository.getUser()
val user = userRepository.getUser()
.asLiveData()
fun scoreTask(task: Task, direction: TaskDirection, onResult: (TaskScoringResult?) -> Unit) {
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)
}
}
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