Fix linting errors

# Conflicts:
#	Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/ClassSelectionActivity.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/preferences/PreferencesFragment.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/support/FAQOverviewFragment.kt
#	Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/AppHeaderView.kt
#	common/src/main/java/com/habitrpg/common/habitica/helpers/MarkdownParser.kt
#	common/src/main/java/com/habitrpg/common/habitica/helpers/NumberAbbreviator.kt
This commit is contained in:
Phillip Thelen 2023-07-31 13:44:16 +02:00
parent 2343ebf5ed
commit 7d230ecfa1
294 changed files with 1881 additions and 1083 deletions

View file

@ -26,6 +26,7 @@ class MainActivityTest : ActivityTestCase() {
val screen = MainActivityScreen()
lateinit var scenario: ActivityScenario<MainActivity>
@After
fun cleanup() {
scenario.close()

View file

@ -26,7 +26,6 @@ import io.github.kakaocup.kakao.toolbar.KToolbar
import io.mockk.coVerify
import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.slot
import io.mockk.verify

View file

@ -26,9 +26,9 @@ class NavigationDrawerScreen : Screen<NavigationDrawerScreen>() {
val recycler: KRecyclerView = KRecyclerView({
withId(R.id.recyclerView)
}, itemTypeBuilder = {
itemType(::SectionHeaderItem)
itemType(::MainItem)
})
itemType(::SectionHeaderItem)
itemType(::MainItem)
})
}
@LargeTest

View file

@ -6,7 +6,6 @@ import android.widget.TextView
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.interactors.HatchPetUseCase
@ -25,7 +24,6 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.spyk
import kotlinx.coroutines.flow.flowOf
import org.hamcrest.Matcher
@ -63,8 +61,8 @@ class ItemScreen : Screen<ItemScreen>() {
val recycler: KRecyclerView = KRecyclerView({
withId(R.id.recyclerView)
}, itemTypeBuilder = {
itemType(::ItemItem)
})
itemType(::ItemItem)
})
}
internal class ItemRecyclerFragmentTest : FragmentTestCase<ItemRecyclerFragment, FragmentRecyclerviewBinding, ItemScreen>(false) {

View file

@ -2,13 +2,11 @@ package com.habitrpg.android.habitica.ui.fragments.inventory.stable
import android.os.Bundle
import androidx.fragment.app.testing.launchFragmentInContainer
import com.habitrpg.android.habitica.HabiticaBaseApplication
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.FragmentRecyclerviewBinding
import com.habitrpg.android.habitica.interactors.FeedPetUseCase
import com.habitrpg.android.habitica.models.user.OwnedItem
import com.habitrpg.android.habitica.ui.fragments.FragmentTestCase
import com.habitrpg.android.habitica.ui.fragments.inventory.items.ItemDialogFragment
import io.github.kakaocup.kakao.common.views.KView
import io.github.kakaocup.kakao.recycler.KRecyclerView
import io.github.kakaocup.kakao.screen.Screen
@ -18,7 +16,6 @@ import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.spyk
import kotlinx.coroutines.flow.flowOf
import org.junit.Test
@ -27,9 +24,9 @@ class PetDetailScreen : Screen<PetDetailScreen>() {
val recycler: KRecyclerView = KRecyclerView({
withId(R.id.recyclerView)
}, itemTypeBuilder = {
itemType(::SectionItem)
itemType(::PetItem)
})
itemType(::SectionItem)
itemType(::PetItem)
})
}
internal class PetDetailRecyclerFragmentTest :

View file

@ -33,9 +33,9 @@ class StableScreen : Screen<StableScreen>() {
val recycler: KRecyclerView = KRecyclerView({
withId(R.id.recyclerView)
}, itemTypeBuilder = {
itemType(::SectionItem)
itemType(::PetItem)
})
itemType(::SectionItem)
itemType(::PetItem)
})
}
internal class StableRecyclerFragmentTest : FragmentTestCase<StableRecyclerFragment, FragmentRecyclerviewBinding, StableScreen>(false) {

View file

@ -31,8 +31,8 @@ class TaskListScreen : Screen<TaskListScreen>() {
val recycler: KRecyclerView = KRecyclerView({
withId(R.id.recyclerView)
}, itemTypeBuilder = {
itemType(::TaskItem)
})
itemType(::TaskItem)
})
}
internal class TaskRecyclerViewFragmentTest : FragmentTestCase<TaskRecyclerViewFragment, FragmentRefreshRecyclerviewBinding, TaskListScreen>(false) {

View file

@ -48,14 +48,19 @@ import javax.inject.Inject
abstract class HabiticaBaseApplication : Application(), Application.ActivityLifecycleCallbacks {
@Inject
internal lateinit var lazyApiHelper: ApiClient
@Inject
internal lateinit var sharedPrefs: SharedPreferences
@Inject
internal lateinit var analyticsManager: AnalyticsManager
@Inject
internal lateinit var pushNotificationManager: PushNotificationManager
@Inject
internal lateinit var authenticationHandler: AuthenticationHandler
/**
* For better performance billing class should be used as singleton
*/

View file

@ -59,6 +59,7 @@ interface ApiService {
@GET("inbox/messages")
suspend fun getInboxMessages(@Query("conversation") uuid: String, @Query("page") page: Int): HabitResponse<List<ChatMessage>>
@GET("inbox/conversations")
suspend fun getInboxConversations(): HabitResponse<List<InboxConversation>>
@ -128,6 +129,7 @@ interface ApiService {
@POST("tasks/{id}/score/{direction}")
suspend fun postTaskDirection(@Path("id") id: String, @Path("direction") direction: String): HabitResponse<TaskDirectionData>
@POST("tasks/bulk-score")
suspend fun bulkScoreTasks(@Body data: List<Map<String, String>>): HabitResponse<BulkTaskScoringData>
@ -139,6 +141,7 @@ interface ApiService {
@POST("tasks/user")
suspend fun createTask(@Body item: Task): HabitResponse<Task>
@POST("tasks/group/{groupId}")
suspend fun createGroupTask(@Path("groupId") groupId: String, @Body item: Task): HabitResponse<Task>
@ -287,8 +290,10 @@ interface ApiService {
suspend fun inviteToQuest(@Path("gid") groupId: String, @Path("questKey") questKey: String): HabitResponse<Quest>
@GET("groups/{gid}/invites")
suspend fun getGroupInvites(@Path("gid") groupId: String,
@Query("includeAllPublicFields") includeAllPublicFields: Boolean?): HabitResponse<List<Member>>
suspend fun getGroupInvites(
@Path("gid") groupId: String,
@Query("includeAllPublicFields") includeAllPublicFields: Boolean?
): HabitResponse<List<Member>>
@POST("groups/{gid}/quests/abort")
suspend fun abortQuest(@Path("gid") groupId: String): HabitResponse<Quest>

View file

@ -277,6 +277,6 @@ interface ApiClient {
suspend fun updateMember(memberID: String, updateData: Map<String, Any?>): Member?
suspend fun getHallMember(userId: String): Member?
suspend fun markTaskNeedsWork(taskID: String, userID: String): Task?
suspend fun retrievePartySeekingUsers(page: Int) : List<Member>?
suspend fun retrievePartySeekingUsers(page: Int): List<Member>?
suspend fun getGroupInvites(groupId: String, includeAllPublicFields: Boolean?): List<Member>?
}

View file

@ -21,7 +21,7 @@ interface UserRepository : BaseRepository {
fun getUser(userID: String): Flow<User?>
suspend fun updateUser(updateData: Map<String, Any?>): User?
suspend fun updateUser(key : String, value : Any?): User?
suspend fun updateUser(key: String, value: Any?): User?
suspend fun retrieveUser(withTasks: Boolean = false, forced: Boolean = false, overrideExisting: Boolean = false): User?

View file

@ -378,7 +378,9 @@ class ApiClientImpl(
override suspend fun validateSubscription(request: PurchaseValidationRequest): Any? {
return if (lastSubscribeCall == null || Date().time - lastSubscribeCall.time > 60000) {
process { apiService.validateSubscription(request) }
} else null
} else {
null
}
}
override suspend fun getHallMember(userId: String): Member? {
@ -629,7 +631,9 @@ class ApiClientImpl(
// make sure a purchase attempt doesn't happen
return if (lastPurchaseValidation == null || Date().time - lastPurchaseValidation.time > 5000) {
return process { apiService.validatePurchase(request) }
} else null
} else {
null
}
}
override suspend fun changeCustomDayStart(updateObject: Map<String, Any>): User? {
@ -640,7 +644,7 @@ class ApiClientImpl(
return process { apiService.markTaskNeedsWork(taskID, userID) }
}
override suspend fun retrievePartySeekingUsers(page: Int) : List<Member>? {
override suspend fun retrievePartySeekingUsers(page: Int): List<Member>? {
return process { apiService.retrievePartySeekingUsers(page) }
}

View file

@ -9,10 +9,10 @@ import com.habitrpg.android.habitica.modules.AuthenticationHandler
abstract class BaseRepositoryImpl<T : BaseLocalRepository>(
protected val localRepository: T,
protected val apiClient: ApiClient,
protected val authenticationHandler : AuthenticationHandler
protected val authenticationHandler: AuthenticationHandler
) : BaseRepository {
val currentUserID : String
val currentUserID: String
get() = authenticationHandler.currentUserID ?: ""
override fun close() {

View file

@ -8,16 +8,16 @@ import com.habitrpg.android.habitica.modules.AuthenticationHandler
import kotlinx.coroutines.flow.Flow
class FAQRepositoryImpl(
localRepository : FAQLocalRepository,
apiClient : ApiClient,
authenticationHandler : AuthenticationHandler
localRepository: FAQLocalRepository,
apiClient: ApiClient,
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<FAQLocalRepository>(localRepository, apiClient, authenticationHandler),
FAQRepository {
override fun getArticle(position : Int) : Flow<FAQArticle> {
override fun getArticle(position: Int): Flow<FAQArticle> {
return localRepository.getArticle(position)
}
override fun getArticles() : Flow<List<FAQArticle>> {
override fun getArticles(): Flow<List<FAQArticle>> {
return localRepository.articles
}
}

View file

@ -52,7 +52,7 @@ class InventoryRepositoryImpl(
return localRepository.getInAppRewards()
}
override fun getInAppReward(key : String) : Flow<ShopItem> {
override fun getInAppReward(key: String): Flow<ShopItem> {
return localRepository.getInAppReward(key)
}
@ -120,7 +120,7 @@ class InventoryRepositoryImpl(
}
override fun getOwnedMounts(): Flow<List<OwnedMount>> {
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedMounts(it) }
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedMounts(it) }
}
override fun getPets(): Flow<List<Pet>> {
@ -132,7 +132,7 @@ class InventoryRepositoryImpl(
}
override fun getOwnedPets(): Flow<List<OwnedPet>> {
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedPets(it) }
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getOwnedPets(it) }
}
override fun updateOwnedEquipment(user: User) {

View file

@ -53,11 +53,11 @@ class SocialRepositoryImpl(
return apiClient.updateMember(memberID, mapOf(key to value))
}
override suspend fun retrievePartySeekingUsers(page: Int) : List<Member>? {
override suspend fun retrievePartySeekingUsers(page: Int): List<Member>? {
return apiClient.retrievePartySeekingUsers(page)
}
override fun getGroupMembership(id: String) = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getGroupMembership(it, id) }
override fun getGroupMembership(id: String) = authenticationHandler.userIDFlow.flatMapLatest { localRepository.getGroupMembership(it, id) }
override fun getGroupMemberships(): Flow<List<GroupMembership>> {
return authenticationHandler.userIDFlow.flatMapLatest { localRepository.getGroupMemberships(it) }
@ -277,7 +277,6 @@ class SocialRepositoryImpl(
override suspend fun retrievegroupInvites(id: String, includeAllPublicFields: Boolean) = apiClient.getGroupInvites(id, includeAllPublicFields)
override suspend fun retrieveMemberWithUsername(username: String?, fromHall: Boolean): Member? {
return retrieveMember(username, fromHall)
}
@ -363,7 +362,9 @@ class SocialRepositoryImpl(
override suspend fun getMemberAchievements(userId: String?): List<Achievement>? {
return if (userId == null) {
null
} else apiClient.getMemberAchievements(userId)
} else {
apiClient.getMemberAchievements(userId)
}
}
override suspend fun transferGems(giftedID: String, amount: Int): Void? {

View file

@ -12,51 +12,51 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
class TagRepositoryImpl(
localRepository : TagLocalRepository,
apiClient : ApiClient,
authenticationHandler : AuthenticationHandler
localRepository: TagLocalRepository,
apiClient: ApiClient,
authenticationHandler: AuthenticationHandler
) : BaseRepositoryImpl<TagLocalRepository>(localRepository, apiClient, authenticationHandler),
TagRepository {
override fun getTags() = authenticationHandler.userIDFlow.flatMapLatest { getTags(it) }
override fun getTags(userId : String) : Flow<List<Tag>> {
override fun getTags(userId: String): Flow<List<Tag>> {
return localRepository.getTags(userId)
}
override suspend fun createTag(tag : Tag) : Tag? {
override suspend fun createTag(tag: Tag): Tag? {
val savedTag = apiClient.createTag(tag) ?: return null
savedTag.userId = currentUserID
localRepository.save(savedTag)
return savedTag
}
override suspend fun updateTag(tag : Tag) : Tag? {
override suspend fun updateTag(tag: Tag): Tag? {
val savedTag = apiClient.updateTag(tag.id, tag) ?: return null
savedTag.userId = currentUserID
localRepository.save(savedTag)
return savedTag
}
override suspend fun deleteTag(id : String) : Void? {
override suspend fun deleteTag(id: String): Void? {
apiClient.deleteTag(id)
localRepository.deleteTag(id)
return null
}
override suspend fun createTags(tags : Collection<Tag>) : List<Tag> {
override suspend fun createTags(tags: Collection<Tag>): List<Tag> {
return tags.mapNotNull {
createTag(it)
}
}
override suspend fun updateTags(tags : Collection<Tag>) : List<Tag> {
override suspend fun updateTags(tags: Collection<Tag>): List<Tag> {
return tags.mapNotNull {
updateTag(it)
}
}
override suspend fun deleteTags(tagIds : Collection<String>) : List<Void> {
override suspend fun deleteTags(tagIds: Collection<String>): List<Void> {
return tagIds.mapNotNull {
deleteTag(it)
}

View file

@ -36,7 +36,7 @@ import java.util.UUID
class TaskRepositoryImpl(
localRepository: TaskLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler,
authenticationHandler: AuthenticationHandler,
val appConfigManager: AppConfigManager,
val analyticsManager: AnalyticsManager
) : BaseRepositoryImpl<TaskLocalRepository>(localRepository, apiClient, authenticationHandler), TaskRepository {
@ -79,7 +79,9 @@ class TaskRepositoryImpl(
): TaskScoringResult? {
val localData = if (user != null && appConfigManager.enableLocalTaskScoring()) {
ScoreTaskLocallyInteractor.score(user, task, if (up) TaskDirection.UP else TaskDirection.DOWN)
} else null
} else {
null
}
if (user != null && localData != null) {
val stats = user.stats
val result = TaskScoringResult(localData, stats)

View file

@ -56,7 +56,7 @@ class UserRepositoryImpl(
return mergeUser(oldUser, networkUser)
}
private suspend fun updateUser(userID : String, key : String, value : Any?): User? {
private suspend fun updateUser(userID: String, key: String, value: Any?): User? {
return updateUser(userID, mapOf(key to value))
}
@ -64,7 +64,7 @@ class UserRepositoryImpl(
return updateUser(currentUserID, updateData)
}
override suspend fun updateUser(key : String, value : Any?): User? {
override suspend fun updateUser(key: String, value: Any?): User? {
return updateUser(currentUserID, key, value)
}
@ -382,9 +382,11 @@ class UserRepositoryImpl(
taskRepository.saveTasks(id, tasksOrder, tasks)
}
val members = apiClient.getGroupMembers(teamID, true) ?: return team
localRepository.save(members.map {
GroupMembership(it.id, id)
})
localRepository.save(
members.map {
GroupMembership(it.id, id)
}
)
members.let { localRepository.save(members) }
return team
}

View file

@ -29,7 +29,8 @@ class RealmTaskLocalRepository(realm: Realm) : RealmBaseLocalRepository(realm),
private fun findTasks(
taskType: TaskType,
ownerID: String): RealmResults<Task> {
ownerID: String
): RealmResults<Task> {
return realm.where(Task::class.java)
.equalTo("typeValue", taskType.value)
.equalTo("ownerID", ownerID)

View file

@ -43,16 +43,24 @@ fun Long.getAgoString(res: Resources): String {
return when {
diffMonths != 0L -> if (diffMonths == 1L) {
res.getString(R.string.ago_1month)
} else res.getString(R.string.ago_months, diffMonths)
} else {
res.getString(R.string.ago_months, diffMonths)
}
diffWeeks != 0L -> if (diffWeeks == 1L) {
res.getString(R.string.ago_1week)
} else res.getString(R.string.ago_weeks, diffWeeks)
} else {
res.getString(R.string.ago_weeks, diffWeeks)
}
diffDays != 0L -> if (diffDays == 1L) {
res.getString(R.string.ago_1day)
} else res.getString(R.string.ago_days, diffDays)
} else {
res.getString(R.string.ago_days, diffDays)
}
diffHours != 0L -> if (diffHours == 1L) {
res.getString(R.string.ago_1hour)
} else res.getString(R.string.ago_hours, diffHours)
} else {
res.getString(R.string.ago_hours, diffHours)
}
diffMinutes == 1L -> res.getString(R.string.ago_1Minute)
else -> res.getString(R.string.ago_minutes, diffMinutes)
}
@ -74,16 +82,24 @@ fun Long.getRemainingString(res: Resources): String {
return when {
diffMonths != 0L -> if (diffMonths == 1L) {
res.getString(R.string.remaining_1month)
} else res.getString(R.string.remaining_months, diffMonths)
} else {
res.getString(R.string.remaining_months, diffMonths)
}
diffWeeks != 0L -> if (diffWeeks == 1L) {
res.getString(R.string.remaining_1week)
} else res.getString(R.string.remaining_weeks, diffWeeks)
} else {
res.getString(R.string.remaining_weeks, diffWeeks)
}
diffDays != 0L -> if (diffDays == 1L) {
res.getString(R.string.remaining_1day)
} else res.getString(R.string.remaining_days, diffDays)
} else {
res.getString(R.string.remaining_days, diffDays)
}
diffHours != 0L -> if (diffHours == 1L) {
res.getString(R.string.remaining_1hour)
} else res.getString(R.string.remaining_hours, diffHours)
} else {
res.getString(R.string.remaining_hours, diffHours)
}
diffMinutes == 1L -> res.getString(R.string.remaining_1Minute)
else -> res.getString(R.string.remaining_minutes, diffMinutes)
}

View file

@ -14,7 +14,8 @@ import java.util.Locale
fun String.parseToZonedDateTime(): ZonedDateTime? {
val parsed: TemporalAccessor = formatter().parseBest(
this,
ZonedDateTime::from, LocalDateTime::from
ZonedDateTime::from,
LocalDateTime::from
)
return if (parsed is ZonedDateTime) {
parsed
@ -40,4 +41,4 @@ fun formatter(): DateTimeFormatter =
.appendPattern("['T'][' ']")
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.appendPattern("[XX]")
.toFormatter()
.toFormatter()

View file

@ -171,7 +171,9 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo
}
RewardedAd.load(
activity, type.adUnitID, adRequest,
activity,
type.adUnitID,
adRequest,
object : RewardedAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
FirebaseCrashlytics.getInstance().recordException(Throwable(adError.message))

View file

@ -0,0 +1,108 @@
package com.habitrpg.android.habitica.helpers
import android.content.Context
import android.content.SharedPreferences
import androidx.core.os.bundleOf
import com.amplitude.android.Amplitude
import com.amplitude.android.Configuration
import com.amplitude.android.events.Identify
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.habitrpg.android.habitica.R
import com.habitrpg.shared.habitica.BuildConfig
enum class AnalyticsTarget {
AMPLITUDE,
FIREBASE
}
enum class EventCategory(val key: String) {
BEHAVIOUR("behaviour"),
NAVIGATION("navigation")
}
enum class HitType(val key: String) {
EVENT("event"),
PAGEVIEW("pageview"),
CREATE_WIDGET("create"),
REMOVE_WIDGET("remove"),
UPDATE_WIDGET("update")
}
object Analytics {
private lateinit var firebase: FirebaseAnalytics
private lateinit var amplitude: Amplitude
@JvmOverloads
fun sendEvent(
eventAction: String?,
category: EventCategory?,
hitType: HitType?,
additionalData: Map<String, Any>? = null,
target: AnalyticsTarget? = null
) {
if (BuildConfig.DEBUG) {
return
}
val data = mutableMapOf<String, Any?>(
"eventAction" to eventAction,
"eventCategory" to category?.key,
"hitType" to hitType?.key,
"status" to "displayed"
)
if (additionalData != null) {
data.putAll(additionalData)
}
if (eventAction != null) {
if (target == null || target == AnalyticsTarget.AMPLITUDE) {
amplitude.track(eventAction, data)
}
if (target == null || target == AnalyticsTarget.FIREBASE) {
firebase.logEvent(eventAction, bundleOf(*data.toList().toTypedArray()))
}
}
}
fun sendNavigationEvent(page: String) {
val additionalData = HashMap<String, Any>()
additionalData["page"] = page
sendEvent("navigated", EventCategory.NAVIGATION, HitType.PAGEVIEW, additionalData)
}
fun initialize(context: Context) {
amplitude = Amplitude(
Configuration(
context.getString(R.string.amplitude_app_id),
context
)
)
firebase = FirebaseAnalytics.getInstance(context)
}
fun identify(sharedPrefs: SharedPreferences) {
val identify = Identify()
.setOnce("androidStore", BuildConfig.STORE)
sharedPrefs.getString("launch_screen", "")?.let {
identify.set("launch_screen", it)
}
amplitude.identify(identify)
}
fun setUserID(userID: String) {
amplitude.setUserId(userID)
FirebaseCrashlytics.getInstance().setUserId(userID)
firebase.setUserId(userID)
}
fun setUserProperty(identifier: String, value: String) {
firebase.setUserProperty(identifier, value)
}
fun logError(msg: String) {
FirebaseCrashlytics.getInstance().log(msg)
}
fun logException(t: Throwable) {
FirebaseCrashlytics.getInstance().recordException(t)
}
}

View file

@ -27,7 +27,6 @@ class NotificationOpenHandler {
PushNotificationManager.G1G1_PROMO_KEY -> openGiftOneGetOneInfoScreen()
else -> {
intent.getStringExtra("openURL")?.let {
MainNavigationController.navigate(it)
}
}

View file

@ -54,26 +54,26 @@ import kotlin.time.DurationUnit
import kotlin.time.toDuration
class PurchaseHandler(
private val context : Context,
private val analyticsManager : AnalyticsManager,
private val apiClient : ApiClient,
private val userViewModel : MainUserViewModel
private val context: Context,
private val analyticsManager: AnalyticsManager,
private val apiClient: ApiClient,
private val userViewModel: MainUserViewModel
) : PurchasesUpdatedListener, PurchasesResponseListener {
private val billingClient =
BillingClient.newBuilder(context).setListener(this).enablePendingPurchases().build()
override fun onPurchasesUpdated(result : BillingResult, purchases : MutableList<Purchase>?) {
override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList<Purchase>?) {
purchases?.let { processPurchases(result, it) }
}
override fun onQueryPurchasesResponse(
result : BillingResult,
purchases : MutableList<Purchase>
result: BillingResult,
purchases: MutableList<Purchase>
) {
processPurchases(result, purchases)
}
private fun processPurchases(result : BillingResult, purchases : List<Purchase>) {
private fun processPurchases(result: BillingResult, purchases: List<Purchase>) {
when (result.responseCode) {
BillingClient.BillingResponseCode.OK -> {
val mostRecentSub = findMostRecentSubscription(purchases)
@ -86,9 +86,10 @@ class PurchaseHandler(
purchase.products.firstOrNull()
)
) {
if (((plan.dateTerminated != null) == purchase.isAutoRenewing)
|| mostRecentSub?.orderId != purchase.orderId
|| purchase.purchaseToken == plan.customerId) {
if (((plan.dateTerminated != null) == purchase.isAutoRenewing) ||
mostRecentSub?.orderId != purchase.orderId ||
purchase.purchaseToken == plan.customerId
) {
continue
}
}
@ -124,12 +125,12 @@ class PurchaseHandler(
startListening()
}
private var billingClientState : BillingClientState = BillingClientState.UNINITIALIZED
private var billingClientState: BillingClientState = BillingClientState.UNINITIALIZED
private enum class BillingClientState {
UNINITIALIZED, READY, UNAVAILABLE, DISCONNECTED, CONNECTING;
val canMaybePurchase : Boolean
val canMaybePurchase: Boolean
get() {
return this == UNINITIALIZED || this == READY || this == CONNECTING
}
@ -150,7 +151,7 @@ class PurchaseHandler(
}
billingClientState = BillingClientState.CONNECTING
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult : BillingResult) {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
billingClientState = BillingClientState.READY
MainScope().launchCatching {
@ -216,18 +217,18 @@ class PurchaseHandler(
suspend fun getAllGiftSubscriptionProducts() =
getSKUs(BillingClient.ProductType.INAPP, PurchaseTypes.allSubscriptionNoRenewTypes)
suspend fun getInAppPurchaseSKU(identifier : String) =
suspend fun getInAppPurchaseSKU(identifier: String) =
getSKU(BillingClient.ProductType.INAPP, identifier)
private suspend fun getSKUs(type : String, identifiers : List<String>) =
private suspend fun getSKUs(type: String, identifiers: List<String>) =
loadInventory(type, identifiers) ?: emptyList()
private suspend fun getSKU(type : String, identifier : String) : ProductDetails? {
private suspend fun getSKU(type: String, identifier: String): ProductDetails? {
val inventory = loadInventory(type, listOf(identifier))
return inventory?.firstOrNull()
}
private suspend fun loadInventory(type : String, skus : List<String>) : List<ProductDetails>? {
private suspend fun loadInventory(type: String, skus: List<String>): List<ProductDetails>? {
retryUntil {
if (billingClientState == BillingClientState.DISCONNECTED) {
startListening()
@ -246,11 +247,11 @@ class PurchaseHandler(
}
fun purchase(
activity : Activity,
skuDetails : ProductDetails,
recipient : String? = null,
recipientUsername : String? = null,
isSaleGemPurchase : Boolean = false
activity: Activity,
skuDetails: ProductDetails,
recipient: String? = null,
recipientUsername: String? = null,
isSaleGemPurchase: Boolean = false
) {
this.isSaleGemPurchase = isSaleGemPurchase
recipient?.let {
@ -268,20 +269,23 @@ class PurchaseHandler(
billingClient.launchBillingFlow(activity, flowParams)
}
private suspend fun consume(purchase : Purchase, retries : Int = 4) {
private suspend fun consume(purchase: Purchase, retries: Int = 4) {
retryUntil { billingClientState.canMaybePurchase && billingClient.isReady }
val params = ConsumeParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build()
val result = billingClient.consumePurchase(params)
if (result.billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
if (result.billingResult.responseCode != BillingClient.BillingResponseCode.OK && retries > 0) {
delay(500)
consume(purchase, retries - 1)
} else if (result.billingResult.responseCode != BillingClient.BillingResponseCode.OK) {
// Throw an error to continue the flow
throw Exception("Failed to consume purchase after multiple attempts")
} else {
userViewModel.userRepository.retrieveUser(false, true)
}
}
private var processedPurchases = mutableSetOf<String>()
private fun handle(purchase : Purchase) {
private fun handle(purchase: Purchase) {
if (purchase.purchaseState != Purchase.PurchaseState.PURCHASED || processedPurchases.contains(purchase.orderId)) {
return
}
@ -299,7 +303,7 @@ class PurchaseHandler(
consume(purchase)
}
displayGryphatriceConfirmationDialog(purchase, gift?.third)
} catch (throwable : Throwable) {
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
}
@ -316,7 +320,7 @@ class PurchaseHandler(
consume(purchase)
}
displayConfirmationDialog(purchase, gift?.third)
} catch (throwable : Throwable) {
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
}
@ -333,7 +337,7 @@ class PurchaseHandler(
consume(purchase)
}
displayConfirmationDialog(purchase, gift?.third)
} catch (throwable : Throwable) {
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
}
@ -350,7 +354,7 @@ class PurchaseHandler(
acknowledgePurchase(purchase)
}
displayConfirmationDialog(purchase)
} catch (throwable : Throwable) {
} catch (throwable: Throwable) {
handleError(throwable, purchase)
}
}
@ -358,7 +362,7 @@ class PurchaseHandler(
}
}
private suspend fun acknowledgePurchase(purchase : Purchase, retries : Int = 4) {
private suspend fun acknowledgePurchase(purchase: Purchase, retries: Int = 4) {
val params =
AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build()
val response = billingClient.acknowledgePurchase(params)
@ -368,13 +372,13 @@ class PurchaseHandler(
}
}
private fun processedPurchase(purchase : Purchase) {
private fun processedPurchase(purchase: Purchase) {
MainScope().launch(ExceptionHandler.coroutine()) {
userViewModel.userRepository.retrieveUser(false, true)
}
}
private fun buildValidationRequest(purchase : Purchase) : PurchaseValidationRequest {
private fun buildValidationRequest(purchase: Purchase): PurchaseValidationRequest {
val validationRequest = PurchaseValidationRequest()
validationRequest.sku = purchase.products.firstOrNull()
validationRequest.transaction = Transaction()
@ -392,17 +396,26 @@ class PurchaseHandler(
return validationRequest
}
private fun handleError(throwable : Throwable, purchase : Purchase) {
(throwable as? HttpException)?.let { error ->
if (error.code() == 401) {
val res = apiClient.getErrorResponse(throwable)
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
processedPurchase(purchase)
removeGift(purchase.products.firstOrNull())
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
private fun handleError(throwable: Throwable, purchase: Purchase) {
when (throwable) {
is HttpException -> {
if (throwable.code() == 401) {
val res = apiClient.getErrorResponse(throwable)
if (res.message != null && res.message == "RECEIPT_ALREADY_USED") {
processedPurchase(purchase)
removeGift(purchase.products.firstOrNull())
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
return
}
return
}
}
else -> {
// Handles other potential errors such as IOException or an exception
// thrown by billingClient.consumePurchase method that is not handled
CoroutineScope(Dispatchers.IO).launch(ExceptionHandler.coroutine()) {
consume(purchase)
}
}
}
@ -410,24 +423,24 @@ class PurchaseHandler(
FirebaseCrashlytics.getInstance().recordException(throwable)
}
suspend fun checkForSubscription() : Purchase? {
suspend fun checkForSubscription(): Purchase? {
val result = withContext(Dispatchers.IO) {
val params =
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.SUBS)
.build()
billingClient.queryPurchasesAsync(params)
}
val fallback : Purchase? = null
val fallback: Purchase? = null
if (result.billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
return findMostRecentSubscription(result.purchasesList)
}
return fallback
}
private fun findMostRecentSubscription(purchasesList : List<Purchase>) : Purchase? {
private fun findMostRecentSubscription(purchasesList: List<Purchase>): Purchase? {
val purchases =
purchasesList.filter { it.isAcknowledged }.sortedByDescending { it.purchaseTime }
var fallback : Purchase? = null
var fallback: Purchase? = null
// If there is a subscription that is still active, prioritise that. Otherwise return the most recent one.
for (purchase in purchases) {
if (purchase.isAutoRenewing) {
@ -440,14 +453,14 @@ class PurchaseHandler(
}
private var alreadyTriedCancellation = false
suspend fun cancelSubscription() : User? {
suspend fun cancelSubscription(): User? {
if (alreadyTriedCancellation) return null
alreadyTriedCancellation = true
apiClient.cancelSubscription()
return userViewModel.userRepository.retrieveUser(false, true)
}
private fun durationString(sku : String) : String {
private fun durationString(sku: String): String {
return when (sku) {
PurchaseTypes.Subscription1MonthNoRenew, PurchaseTypes.Subscription1Month -> "1"
PurchaseTypes.Subscription3MonthNoRenew, PurchaseTypes.Subscription3Month -> "3"
@ -459,7 +472,7 @@ class PurchaseHandler(
private var isSaleGemPurchase = false
private fun gemAmountString(sku : String) : String {
private fun gemAmountString(sku: String): String {
if (isSaleGemPurchase) {
isSaleGemPurchase = false
return when (sku) {
@ -482,7 +495,7 @@ class PurchaseHandler(
private val displayedConfirmations = mutableListOf<String>()
private fun displayConfirmationDialog(purchase : Purchase, giftedTo : String? = null) {
private fun displayConfirmationDialog(purchase: Purchase, giftedTo: String? = null) {
if (displayedConfirmations.contains(purchase.orderId)) {
return
}
@ -496,7 +509,9 @@ class PurchaseHandler(
PurchaseTypes.allSubscriptionNoRenewTypes.contains(sku) -> {
title = context.getString(R.string.gift_confirmation_title)
context.getString(
R.string.gift_confirmation_text_sub, giftedTo, durationString(sku)
R.string.gift_confirmation_text_sub,
giftedTo,
durationString(sku)
)
}
@ -505,7 +520,8 @@ class PurchaseHandler(
context.getString(R.string.subscription_confirmation)
} else {
context.getString(
R.string.subscription_confirmation_multiple, durationString(sku)
R.string.subscription_confirmation_multiple,
durationString(sku)
)
}
}
@ -513,7 +529,9 @@ class PurchaseHandler(
PurchaseTypes.allGemTypes.contains(sku) && giftedTo != null -> {
title = context.getString(R.string.gift_confirmation_title)
context.getString(
R.string.gift_confirmation_text_gems_new, giftedTo, gemAmountString(sku)
R.string.gift_confirmation_text_gems_new,
giftedTo,
gemAmountString(sku)
)
}
@ -539,8 +557,8 @@ class PurchaseHandler(
}
private fun displayGryphatriceConfirmationDialog(
purchase : Purchase,
giftedTo : String? = null
purchase: Purchase,
giftedTo: String? = null
) {
MainScope().launch(ExceptionHandler.coroutine()) {
val application = (context as? HabiticaBaseApplication)
@ -568,15 +586,15 @@ class PurchaseHandler(
companion object {
private const val PENDING_GIFTS_KEY = "PENDING_GIFTS_DATED"
private var pendingGifts : MutableMap<String, Triple<Date, String, String>> = HashMap()
private var preferences : SharedPreferences? = null
private var pendingGifts: MutableMap<String, Triple<Date, String, String>> = HashMap()
private var preferences: SharedPreferences? = null
fun addGift(sku : String, userID : String, username : String) {
fun addGift(sku: String, userID: String, username: String) {
pendingGifts[sku] = Triple(Date(), userID, username)
savePendingGifts()
}
private fun removeGift(sku : String?) : Triple<Date, String, String>? {
private fun removeGift(sku: String?): Triple<Date, String, String>? {
val gift = pendingGifts.remove(sku)
savePendingGifts()
return gift
@ -593,11 +611,11 @@ class PurchaseHandler(
}
suspend fun retryUntil(
times : Int = Int.MAX_VALUE,
initialDelay : Long = 100, // 0.1 second
maxDelay : Long = 1000, // 1 second
factor : Double = 2.0,
block : suspend () -> Boolean
times: Int = Int.MAX_VALUE,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 1000, // 1 second
factor: Double = 2.0,
block: suspend () -> Boolean
) {
var currentDelay = initialDelay
repeat(times - 1) {

View file

@ -31,7 +31,7 @@ import java.util.Date
class TaskAlarmManager(
private var context: Context,
private var taskRepository: TaskRepository,
private var authenticationHandler : AuthenticationHandler
private var authenticationHandler: AuthenticationHandler
) {
private val am: AlarmManager? = context.getSystemService(Context.ALARM_SERVICE) as? AlarmManager

View file

@ -130,7 +130,8 @@ class TaskDescriptionBuilder(private val context: Context) {
Frequency.WEEKLY -> context.resources.getQuantityString(R.plurals.repeat_weekly, everyX, everyX)
Frequency.MONTHLY -> context.resources.getQuantityString(
R.plurals.repeat_monthly,
everyX, everyX
everyX,
everyX
)
Frequency.YEARLY -> context.resources.getQuantityString(R.plurals.repeat_yearly, everyX, everyX)
null -> ""

View file

@ -34,7 +34,9 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
val existingNotifications = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notificationManager?.activeNotifications?.filter { it.id == getNotificationID(data) }
} else null
} else {
null
}
val oldMessages = existingNotifications?.firstOrNull()?.notification?.extras?.getBundle("messages")?.get("messages") as? ArrayList<Map<String, String>> ?: arrayListOf()
for (oldMessage in oldMessages) {
style = style.addMessage(makeMessageFromData(oldMessage))
@ -75,7 +77,8 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
intent.putExtra("NOTIFICATION_ID", notificationId)
val replyPendingIntent: PendingIntent =
PendingIntent.getBroadcast(
context, groupID.hashCode(),
context,
groupID.hashCode(),
intent,
withMutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
@ -83,7 +86,8 @@ class GroupActivityNotification(context: Context, identifier: String?) : Habitic
val action: NotificationCompat.Action =
NotificationCompat.Action.Builder(
R.drawable.ic_send_grey_600_24dp,
context.getString(R.string.reply), replyPendingIntent
context.getString(R.string.reply),
replyPendingIntent
)
.addRemoteInput(remoteInput)
.build()

View file

@ -19,7 +19,9 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
val existingNotifications = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notificationManager?.activeNotifications?.filter { it.id == getNotificationID(data) }
} else null
} else {
null
}
val messageText = EmojiParser.parseEmojis(data["message"]?.trim { it <= ' ' })
val oldMessages = existingNotifications?.firstOrNull()?.notification?.extras?.getStringArrayList("messages") ?: arrayListOf()
var style = NotificationCompat.InboxStyle()
@ -73,7 +75,8 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
intent.putExtra("NOTIFICATION_ID", notificationId)
val replyPendingIntent: PendingIntent =
PendingIntent.getBroadcast(
context, senderID.hashCode(),
context,
senderID.hashCode(),
intent,
withMutableFlag(PendingIntent.FLAG_UPDATE_CURRENT)
)
@ -81,7 +84,8 @@ class ReceivedPrivateMessageLocalNotification(context: Context, identifier: Stri
val action: NotificationCompat.Action =
NotificationCompat.Action.Builder(
R.drawable.ic_send_grey_600_24dp,
context.getString(R.string.reply), replyPendingIntent
context.getString(R.string.reply),
replyPendingIntent
)
.addRemoteInput(remoteInput)
.build()

View file

@ -10,7 +10,7 @@ import javax.inject.Inject
class BuyRewardUseCase @Inject
constructor(
private val taskRepository: TaskRepository,
private val soundManager: SoundManager,
private val soundManager: SoundManager
) : UseCase<BuyRewardUseCase.RequestValues, TaskScoringResult?>() {
override suspend fun run(requestValues: RequestValues): TaskScoringResult? {

View file

@ -21,8 +21,9 @@ constructor(private val soundManager: SoundManager) :
val snackbarText = StringBuilder(data?.drop?.dialog ?: "")
if ((data?.questItemsFound ?: 0) > 0 && requestValues.showQuestItems) {
if (snackbarText.isNotEmpty())
if (snackbarText.isNotEmpty()) {
snackbarText.append('\n')
}
snackbarText.append(
requestValues.context.getString(
R.string.quest_items_found,
@ -36,7 +37,9 @@ constructor(private val soundManager: SoundManager) :
delay(3000L)
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,
snackbarText, HabiticaSnackbar.SnackbarDisplayType.DROP, true
snackbarText,
HabiticaSnackbar.SnackbarDisplayType.DROP,
true
)
soundManager.loadAndPlayAudio(SoundManager.SoundItemDrop)
}

View file

@ -19,7 +19,7 @@ import javax.inject.Inject
class FeedPetUseCase @Inject
constructor(
private val inventoryRepository: InventoryRepository,
private val inventoryRepository: InventoryRepository
) : UseCase<FeedPetUseCase.RequestValues, FeedResponse?>() {
override suspend fun run(requestValues: FeedPetUseCase.RequestValues): FeedResponse? {
val feedResponse = inventoryRepository.feedPet(requestValues.pet, requestValues.food)

View file

@ -22,4 +22,4 @@ class InsufficientGemsUseCase @Inject constructor(
}
class RequestValues(val gemPrice: Int, val activity: Activity) : UseCase.RequestValues
}
}

View file

@ -52,7 +52,8 @@ constructor(
HabiticaSnackbar.showSnackbar(
requestValues.snackbarTargetView,
requestValues.activity.getString(R.string.levelup_header, requestValues.newLevel),
HabiticaSnackbar.SnackbarDisplayType.SUCCESS, true
HabiticaSnackbar.SnackbarDisplayType.SUCCESS,
true
)
return requestValues.user.stats
}

View file

@ -74,7 +74,6 @@ constructor(
questDamage: Double?,
user: User?
): Pair<View, SnackbarDisplayType> {
var displayType = SnackbarDisplayType.SUCCESS
val container = LinearLayout(context)

View file

@ -10,8 +10,10 @@ open class TeamPlan : RealmObject(), BaseObject {
var userID: String? = null
var summary: String = ""
@SerializedName("leader")
var leaderID: String? = null
// var managers: RealmList<String> = RealmList()
var isActive: Boolean = false

View file

@ -41,7 +41,7 @@ open class TutorialStep : RealmObject(), BaseMainObject {
val flagPath: String
get() = "flags.tutorial.$tutorialGroup.$identifier"
val linkFAQ : Boolean
val linkFAQ: Boolean
get() {
return identifier == "party"
}

View file

@ -18,6 +18,7 @@ open class WorldState : RealmObject(), BaseObject {
var npcImageSuffix: String? = null
var currentEvent: WorldStateEvent? = null
@SerializedName("currentEventList")
var events: RealmList<WorldStateEvent> = RealmList()
}

View file

@ -10,6 +10,7 @@ import io.realm.annotations.RealmClass
open class LocalAuthentication : RealmObject(), BaseObject, AvatarLocalAuthentication {
override var username: String? = null
var email: String? = null
@SerializedName("has_password")
var hasPassword: Boolean? = false
}

View file

@ -61,10 +61,12 @@ open class Customization : RealmObject(), BaseObject {
"hair" -> {
return if (identifier == "0") {
"head_0"
} else when (this.category) {
"color" -> "hair_bangs_1_$identifier"
"flower" -> "hair_flower_$identifier"
else -> "hair_" + this.category + "_" + identifier + "_" + hairColor
} else {
when (this.category) {
"color" -> "hair_bangs_1_$identifier"
"flower" -> "hair_flower_$identifier"
else -> "hair_" + this.category + "_" + identifier + "_" + hairColor
}
}
}
"background" -> return "background_$identifier"

View file

@ -10,6 +10,7 @@ open class Equipment : RealmObject(), BaseMainObject {
var value: Double = 0.toDouble()
var type: String? = ""
@PrimaryKey
var key: String? = ""
var klass: String = ""
@ -20,6 +21,7 @@ open class Equipment : RealmObject(), BaseMainObject {
var con: Int = 0
var str: Int = 0
var per: Int = 0
@SerializedName("int")
var _int: Int = 0
var owned: Boolean? = null

View file

@ -11,13 +11,17 @@ open class Mount : RealmObject(), Animal {
get() {
return if (field.isBlank()) {
key?.split("-")?.toTypedArray()?.get(0) ?: ""
} else field
} else {
field
}
}
override var color: String = ""
get() {
return if (field.isBlank()) {
key?.split("-")?.toTypedArray()?.get(1) ?: ""
} else field
} else {
field
}
}
override var text: String? = null
override var type: String? = null
@ -25,6 +29,7 @@ open class Mount : RealmObject(), Animal {
@Ignore
override var numberOwned: Int = 0
@Ignore
override var totalNumber: Int = 0
}

View file

@ -11,13 +11,17 @@ open class Pet : RealmObject(), Animal {
get() {
return if (field.isBlank()) {
key?.split("-")?.toTypedArray()?.get(0) ?: ""
} else field
} else {
field
}
}
override var color: String = ""
get() {
return if (field.isBlank()) {
key?.split("-")?.toTypedArray()?.get(1) ?: ""
} else field
} else {
field
}
}
override var text: String? = null
override var type: String? = null
@ -25,6 +29,7 @@ open class Pet : RealmObject(), Animal {
@Ignore
override var numberOwned: Int = 0
@Ignore
override var totalNumber: Int = 0
}

View file

@ -1,5 +1,3 @@
package com.habitrpg.android.habitica.models.invitations
class InviteResponse {
}
class InviteResponse

View file

@ -26,7 +26,9 @@ open class MemberPreferences :
} else {
"chair_$field"
}
} else null
} else {
null
}
}
var language: String? = null
}

View file

@ -8,16 +8,20 @@ import com.habitrpg.shared.habitica.models.responses.TaskDirectionData
class BulkTaskScoringData {
@SerializedName("con")
var constitution: Int? = null
@SerializedName("str")
var strength: Int? = null
@SerializedName("per")
var per: Int? = null
@SerializedName("int")
var intelligence: Int? = null
var training: Training? = null
var buffs: Buffs? = null
var points: Int? = null
var lvl: Int? = null
@SerializedName("class")
var habitClass: String? = null
var gp: Double? = null

View file

@ -18,6 +18,7 @@ open class ShopItem : RealmObject(), BaseObject {
var key: String = ""
var text: String? = ""
var notes: String? = ""
@SerializedName("class")
var imageName: String? = null
get() {
@ -44,9 +45,11 @@ open class ShopItem : RealmObject(), BaseObject {
var unlockPath: String? = null
var isSuggested: String? = null
var pinType: String? = null
@SerializedName("klass")
var habitClass: String? = null
var previous: String? = null
@SerializedName("lvl")
var level: Int? = null
var event: ItemEvent? = null

View file

@ -33,6 +33,7 @@ open class Challenge : RealmObject(), BaseMainObject {
var group: Group? = null
var leader: User? = null
@Ignore
var tasksOrder: TasksOrder? = null
var summary: String? = null
@ -69,7 +70,9 @@ open class Challenge : RealmObject(), BaseMainObject {
override fun equals(other: Any?): Boolean {
return if (other?.javaClass == Challenge::class.java && this.id != null) {
this.id == (other as Challenge).id
} else super.equals(other)
} else {
super.equals(other)
}
}
override fun hashCode(): Int {

View file

@ -13,6 +13,7 @@ open class UserParty : RealmObject(), BaseObject {
var id: String = ""
var quest: Quest? = null
var seeking: Date? = null
@SerializedName("order")
var partyOrder: String? = null // Order to display ppl
var orderAscending: String? = null // Order type

View file

@ -66,7 +66,9 @@ open class ChecklistItem : RealmObject, BaseMainObject, Parcelable {
override fun equals(other: Any?): Boolean {
return if (other is ChecklistItem) {
this.id == other.id
} else super.equals(other)
} else {
super.equals(other)
}
}
override fun hashCode(): Int {

View file

@ -51,7 +51,9 @@ open class RemindersItem : RealmObject, Parcelable {
override fun equals(other: Any?): Boolean {
return if (other is RemindersItem) {
this.id == other.id
} else super.equals(other)
} else {
super.equals(other)
}
}
override fun hashCode(): Int {
@ -71,7 +73,8 @@ open class RemindersItem : RealmObject, Parcelable {
val parsed: TemporalAccessor = formatter.parseBest(
time,
ZonedDateTime::from, LocalDateTime::from
ZonedDateTime::from,
LocalDateTime::from
)
return if (parsed is ZonedDateTime) {
parsed
@ -91,7 +94,8 @@ open class RemindersItem : RealmObject, Parcelable {
val parsed: TemporalAccessor = formatter.parseBest(
time,
ZonedDateTime::from, LocalDateTime::from
ZonedDateTime::from,
LocalDateTime::from
)
return if (parsed is ZonedDateTime) {
parsed.withZoneSameLocal(ZoneId.systemDefault())?.toInstant()

View file

@ -69,15 +69,18 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
var dateCreated: Date? = null
var position: Int = 0
var group: TaskGroupPlan? = null
// Habits
var up: Boolean? = false
var down: Boolean? = false
override var counterUp: Int? = 0
override var counterDown: Int? = 0
// todos/dailies
override var completed: Boolean = false
var checklist: RealmList<ChecklistItem>? = RealmList()
var reminders: RealmList<RemindersItem>? = RealmList()
// dailies
var frequency: Frequency?
get() = Frequency.from(frequencyValue)
@ -87,11 +90,14 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
override var streak: Int? = 0
var startDate: Date? = null
var repeat: Days? = null
// todos
@SerializedName("date")
var dueDate: Date? = null
@Ignore
var parsedText: Spanned? = null
@Ignore
var parsedNotes: Spanned? = null
@ -370,8 +376,6 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
}
}
fun parseMarkdown() {
parsedText = MarkdownParser.parseMarkdown(text)
parsedNotes = MarkdownParser.parseMarkdown(notes)
@ -421,7 +425,6 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
}
fun isBeingEdited(task: Task): Boolean {
when {
text != task.text -> return true
notes != task.notes -> return true

View file

@ -22,12 +22,16 @@ open class Authentication : RealmObject(), BaseObject, AvatarAuthentication {
var blocked: Boolean = false
val hasPassword: Boolean
get() = localAuthentication?.hasPassword == true
@SerializedName("local")
override var localAuthentication: LocalAuthentication? = null
@SerializedName("google")
var googleAuthentication: SocialAuthentication? = null
@SerializedName("apple")
var appleAuthentication: SocialAuthentication? = null
@SerializedName("facebook")
var facebookAuthentication: SocialAuthentication? = null

View file

@ -10,6 +10,7 @@ import java.util.Date
open class AuthenticationTimestamps : RealmObject(), BaseObject {
@SerializedName("loggedin")
var lastLoggedIn: Date? = null
@SerializedName("created")
var createdAt: Date? = null
}

View file

@ -14,6 +14,7 @@ open class Outfit : RealmObject(), BaseObject, AvatarOutfit {
override var head: String = ""
override var shield: String = ""
override var weapon: String = ""
@SerializedName("eyewear")
override var eyeWear: String = ""
override var headAccessory: String = ""

View file

@ -11,8 +11,10 @@ open class Preferences : RealmObject(), AvatarPreferences, BaseObject {
override var hair: Hair? = null
var suppressModals: SuppressedModals? = null
override var costume: Boolean = false
@SerializedName("disableClasses")
override var disableClasses: Boolean = false
@SerializedName("sleep")
override var sleep: Boolean = false
var dailyDueDefaultView: Boolean = false
@ -30,7 +32,9 @@ open class Preferences : RealmObject(), AvatarPreferences, BaseObject {
} else {
"chair_" + field!!
}
} else "chair_none"
} else {
"chair_none"
}
}
var language: String? = null
var sound: String? = null

View file

@ -10,16 +10,20 @@ import io.realm.annotations.RealmClass
open class Stats : RealmObject(), AvatarStats, BaseObject {
@SerializedName("con")
var constitution: Int? = null
@SerializedName("str")
var strength: Int? = null
@SerializedName("per")
var per: Int? = null
@SerializedName("int")
var intelligence: Int? = null
var training: Training? = null
override var buffs: Buffs? = null
override var points: Int? = null
override var lvl: Int? = null
@SerializedName("class")
override var habitClass: String? = null
override var gp: Double? = null

View file

@ -43,7 +43,9 @@ open class SubscriptionPlan : RealmObject(), BaseObject {
get() {
return if (isActive) {
25 + (consecutive?.gemCapExtra ?: 0)
} else 0
} else {
0
}
}
val numberOfGemsLeft: Int

View file

@ -58,6 +58,7 @@ open class User : RealmObject(), BaseMainObject, Avatar, VersionedObject {
var profile: Profile? = null
var party: UserParty? = null
override var items: Items? = null
@SerializedName("auth")
override var authentication: Authentication? = null
override var flags: Flags? = null

View file

@ -60,10 +60,11 @@ class AppModule {
): KeyHelper? {
return if (keyStore == null) {
null
} else getInstance(context, sharedPreferences, keyStore)
} else {
getInstance(context, sharedPreferences, keyStore)
}
}
@Provides
@Singleton
fun providesAuthenticationHandler(sharedPreferences: SharedPreferences): AuthenticationHandler {

View file

@ -31,7 +31,7 @@ open class RepositoryModule {
contentLocalRepository: ContentLocalRepository,
apiClient: ApiClient,
@ApplicationContext context: Context,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): ContentRepository {
return ContentRepositoryImpl(
contentLocalRepository,

View file

@ -31,7 +31,7 @@ class AuthenticationHandler {
val isAuthenticated: Boolean
get() = currentUserID != null
constructor(sharedPreferences : SharedPreferences) {
constructor(sharedPreferences: SharedPreferences) {
_userIDFlow.value = sharedPreferences.getString("UserID", "") ?: ""
}

View file

@ -69,7 +69,7 @@ class UserRepositoryModule {
fun providesTaskRepository(
localRepository: TaskLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler,
authenticationHandler: AuthenticationHandler,
appConfigManager: AppConfigManager,
analyticsManager: AnalyticsManager
): TaskRepository {
@ -91,7 +91,7 @@ class UserRepositoryModule {
fun providesTagRepository(
localRepository: TagLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): TagRepository {
return TagRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@ -105,7 +105,7 @@ class UserRepositoryModule {
fun providesChallengeRepository(
localRepository: ChallengeLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): ChallengeRepository {
return ChallengeRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@ -119,7 +119,7 @@ class UserRepositoryModule {
fun providesUserRepository(
localRepository: UserLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler,
authenticationHandler: AuthenticationHandler,
taskRepository: TaskRepository,
appConfigManager: AppConfigManager,
analyticsManager: AnalyticsManager
@ -143,14 +143,15 @@ class UserRepositoryModule {
fun providesSocialRepository(
localRepository: SocialLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): SocialRepository {
return SocialRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@Provides
fun providesInventoryLocalRepository(
realm: Realm): InventoryLocalRepository {
realm: Realm
): InventoryLocalRepository {
return RealmInventoryLocalRepository(realm)
}
@ -158,7 +159,7 @@ class UserRepositoryModule {
fun providesInventoryRepository(
localRepository: InventoryLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler,
authenticationHandler: AuthenticationHandler,
remoteConfig: AppConfigManager
): InventoryRepository {
return InventoryRepositoryImpl(localRepository, apiClient, authenticationHandler, remoteConfig)
@ -173,7 +174,7 @@ class UserRepositoryModule {
fun providesFAQRepository(
localRepository: FAQLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): FAQRepository {
return FAQRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@ -187,7 +188,7 @@ class UserRepositoryModule {
fun providesTutorialRepository(
localRepository: TutorialLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): TutorialRepository {
return TutorialRepositoryImpl(localRepository, apiClient, authenticationHandler)
}
@ -201,7 +202,7 @@ class UserRepositoryModule {
fun providesCustomizationRepository(
localRepository: CustomizationLocalRepository,
apiClient: ApiClient,
authenticationHandler : AuthenticationHandler
authenticationHandler: AuthenticationHandler
): CustomizationRepository {
return CustomizationRepositoryImpl(localRepository, apiClient, authenticationHandler)
}

View file

@ -32,8 +32,10 @@ class NotificationPublisher : BroadcastReceiver() {
@Inject
lateinit var taskRepository: TaskRepository
@Inject
lateinit var userRepository: UserRepository
@Inject
lateinit var sharedPreferences: SharedPreferences
@ -44,7 +46,7 @@ class NotificationPublisher : BroadcastReceiver() {
this.context = context
if (!wasInjected) {
wasInjected = true
}
}
var wasInactive = false
// Show special notification if user hasn't logged in for a week
@ -121,8 +123,10 @@ class NotificationPublisher : BroadcastReceiver() {
notificationIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
val intent = PendingIntent.getActivity(
thisContext, 0,
notificationIntent, withImmutableFlag(0)
thisContext,
0,
notificationIntent,
withImmutableFlag(0)
)
builder.setContentIntent(intent)

View file

@ -18,6 +18,7 @@ class TaskAlarmBootReceiver : BroadcastReceiver() {
@Inject
lateinit var taskAlarmManager: TaskAlarmManager
@Inject
lateinit var sharedPreferences: SharedPreferences

View file

@ -17,7 +17,8 @@ class GemPurchaseOptionsView(context: Context, attrs: AttributeSet) : FrameLayou
val a = context.theme.obtainStyledAttributes(
attrs,
R.styleable.GemPurchaseOptionsView,
0, 0
0,
0
)
binding.gemAmount.text = a.getText(R.styleable.GemPurchaseOptionsView_gemAmount)

View file

@ -16,7 +16,8 @@ class SpeechBubbleView(context: Context, attrs: AttributeSet) : FrameLayout(cont
val attributes = context.theme.obtainStyledAttributes(
attrs,
R.styleable.SpeechBubbleView,
0, 0
0,
0
)
binding.namePlate.text = attributes.getString(R.styleable.SpeechBubbleView_namePlate)

View file

@ -75,7 +75,9 @@ class AdventureGuideActivity : BaseActivity() {
return if (item.itemId == android.R.id.home) {
NavUtils.navigateUpFromSameTask(this)
true
} else super.onOptionsItemSelected(item)
} else {
super.onOptionsItemSelected(item)
}
}
private fun updateUser(user: User) {

View file

@ -42,8 +42,10 @@ class ArmoireActivity : BaseActivity() {
@Inject
internal lateinit var inventoryRepository: InventoryRepository
@Inject
internal lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel
@ -156,7 +158,8 @@ class ArmoireActivity : BaseActivity() {
createParticles(container, R.drawable.confetti_red)
createParticles(container, R.drawable.confetti_yellow)
createParticles(container, R.drawable.confetti_purple)
}, 500
},
500
)
binding.iconView.startAnimation(Animations.bobbingAnimation())

View file

@ -41,8 +41,10 @@ import javax.inject.Inject
abstract class BaseActivity : AppCompatActivity() {
@Inject
lateinit var notificationsManager: NotificationsManager
@Inject
lateinit var userRepository: UserRepository
@Inject
internal lateinit var analyticsManager: AnalyticsManager

View file

@ -559,7 +559,8 @@ fun PotionGrid() {
model = DataBindingUtils.BASE_IMAGE_URL + DataBindingUtils.getFullFilename(
"Pet_HatchingPotion_$potion"
),
null, Modifier.size(68.dp)
null,
Modifier.size(68.dp)
)
}
}

View file

@ -15,12 +15,9 @@ import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.AppCompatCheckedTextView
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.ChallengeRepository
import com.habitrpg.android.habitica.data.SocialRepository
@ -230,7 +227,10 @@ class ChallengeFormActivity : BaseActivity() {
tasksViewModel = ViewModelProvider(this)[TasksViewModel::class.java]
ChallengeTasksRecyclerViewAdapter(
tasksViewModel, 0, this, "",
tasksViewModel,
0,
this,
"",
openTaskDisabled = false,
taskActionsDisabled = true
).also { challengeTasks = it }
@ -499,7 +499,9 @@ class ChallengeFormActivity : BaseActivity() {
taskList.remove(addReward)
return challengeRepository.updateChallenge(
c, taskList, ArrayList(addedTasks.values),
c,
taskList,
ArrayList(addedTasks.values),
ArrayList(updatedTasks.values),
ArrayList(removedTasks.keys)
)

View file

@ -12,6 +12,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.navArgs
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityClassSelectionBinding
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.user.Gear
import com.habitrpg.android.habitica.models.user.Items
import com.habitrpg.android.habitica.models.user.Outfit
@ -52,7 +53,7 @@ class ClassSelectionActivity : BaseActivity() {
binding.selectedTitleTextView.text = getString(R.string.x_class, className)
binding.selectedButton.text = getString(R.string.become_x, className)
}
private var isInitialSelection: Boolean = false
private var isClassSelected: Boolean = false
private var classWasUnset: Boolean? = false
private var shouldFinish: Boolean? = false
@ -74,9 +75,8 @@ class ClassSelectionActivity : BaseActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true)
val args = navArgs<ClassSelectionActivityArgs>().value
isInitialSelection = args.isInitialSelection
isClassSelected = args.isClassSelected
currentClass = args.className
newClass = currentClass ?: "healer"
userViewModel.user.observe(this) {
@ -87,15 +87,8 @@ class ClassSelectionActivity : BaseActivity() {
}
}
if (!isInitialSelection) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.changeClass()
classWasUnset
}
}
binding.healerWrapper.setOnClickListener { newClass = "healer" }
binding.mageWrapper.setOnClickListener { newClass = "wizard" }
binding.mageWrapper.setOnClickListener { newClass = "mage" }
binding.rogueWrapper.setOnClickListener { newClass = "rogue" }
binding.warriorWrapper.setOnClickListener { newClass = "warrior" }
binding.selectedButton.setOnClickListener { displayConfirmationDialogForClass() }
@ -212,30 +205,34 @@ class ClassSelectionActivity : BaseActivity() {
}
private fun optOutSelected() {
if (!this.isInitialSelection && this.classWasUnset == false) {
return
}
val alert = HabiticaAlertDialog(this)
alert.setTitle(getString(R.string.opt_out_confirmation))
alert.addButton(R.string.opt_out_class, true) { _, _ -> optOutOfClasses() }
alert.addButton(R.string.dialog_go_back, false)
alert.setMessage(getString(R.string.opt_out_description))
alert.addButton(R.string.opt_out_class, true, true) { _, _ ->
lifecycleScope.launch(ExceptionHandler.coroutine()) {
// Set Player to have no class, and opt out
classWasUnset
optOutOfClasses()
}
}
alert.addButton(R.string.close, false)
alert.show()
}
private fun displayConfirmationDialogForClass() {
if (!this.isInitialSelection && this.classWasUnset == false) {
if (isClassSelected) {
val alert = HabiticaAlertDialog(this)
alert.setTitle(getString(R.string.change_class_selected_confirmation, newClass))
alert.setMessage(getString(R.string.change_class_equipment_warning))
alert.setTitle(getString(R.string.change_class_selected_confirmation, className))
alert.setMessage(getString(R.string.change_class_confirmation_message))
alert.addButton(R.string.choose_class, true) { _, _ ->
selectClass(newClass, true)
selectClass(newClass)
}
alert.addButton(R.string.dialog_go_back, false)
alert.show()
} else {
val alert = HabiticaAlertDialog(this)
alert.setTitle(getString(R.string.class_confirmation, className))
alert.addButton(R.string.choose_class, true) { _, _ -> selectClass(newClass, false) }
alert.addButton(R.string.choose_class, true) { _, _ -> selectClass(newClass) }
alert.addButton(R.string.dialog_go_back, false)
alert.show()
}
@ -244,8 +241,12 @@ class ClassSelectionActivity : BaseActivity() {
private fun displayClassChanged(selectedClass: String) {
val alert = HabiticaAlertDialog(this)
alert.setTitle(getString(R.string.class_changed, className))
alert.setMessage(getString(R.string.class_changed_description, selectedClass))
alert.addButton(getString(R.string.complete_tutorial), true){ _, _ -> dismiss() }
alert.setMessage(getString(R.string.class_changed_description, className))
alert.addButton(getString(R.string.complete_tutorial), true) { _, _ -> dismiss() }
alert.addButton(getString(R.string.learn_more), false) { _, _ ->
dismiss()
MainNavigationController.navigate(R.id.FAQOverviewFragment)
}
alert.setOnCancelListener {
dismiss()
}
@ -261,17 +262,27 @@ class ClassSelectionActivity : BaseActivity() {
}
}
private fun selectClass(selectedClass: String, isChanging: Boolean) {
private fun selectClass(selectedClass: String) {
shouldFinish = true
val dialog = this.displayProgressDialog(getString(R.string.changing_class_progress))
lifecycleScope.launch(Dispatchers.Main) {
userRepository.changeClass(selectedClass)
dialog.hide()
if (isChanging) displayClassChanged(selectedClass)
val chosenClass = if (selectedClass == "mage") "wizard" else selectedClass
if (isClassSelected) {
val dialog = this.displayProgressDialog(getString(R.string.changing_class_progress))
lifecycleScope.launch(Dispatchers.Main) {
userRepository.changeClass(chosenClass)
dialog.hide()
displayClassChanged(chosenClass)
}
} else {
val dialog = this.displayProgressDialog(getString(R.string.choosing_class_progress))
lifecycleScope.launch(Dispatchers.Main) {
userRepository.changeClass(chosenClass)
dialog.hide()
displayClassChanged(chosenClass)
}
}
}
private fun displayProgressDialog(progressText: String) : HabiticaProgressDialog {
private fun displayProgressDialog(progressText: String): HabiticaProgressDialog {
return HabiticaProgressDialog.show(this, progressText, 300)
}

View file

@ -30,8 +30,10 @@ class DeathActivity : BaseActivity() {
@Inject
internal lateinit var inventoryRepository: InventoryRepository
@Inject
internal lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var userViewModel: MainUserViewModel

View file

@ -72,6 +72,7 @@ class FullProfileActivity : BaseActivity() {
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var sharedPrefs: SharedPreferences
@ -535,7 +536,11 @@ class FullProfileActivity : BaseActivity() {
private fun addLevelAttributes(user: Member) {
val byLevelStat = min((user.stats?.lvl ?: 0) / 2.0f, 50f)
addAttributeRow(
getString(R.string.profile_level), byLevelStat, byLevelStat, byLevelStat, byLevelStat,
getString(R.string.profile_level),
byLevelStat,
byLevelStat,
byLevelStat,
byLevelStat,
roundDown = true,
isSummary = false
)
@ -684,10 +689,11 @@ class FullProfileActivity : BaseActivity() {
binding.attributesCollapseIcon.setImageDrawable(
ContextCompat.getDrawable(
this,
if (attributeDetailsHidden)
if (attributeDetailsHidden) {
R.drawable.ic_keyboard_arrow_right_black_24dp
else
} else {
R.drawable.ic_keyboard_arrow_down_black_24dp
}
)
)

View file

@ -15,7 +15,6 @@ class GemPurchaseActivity : PurchaseActivity() {
return R.layout.activity_gem_purchase
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -34,8 +34,10 @@ class GiftGemsActivity : PurchaseActivity() {
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var purchaseHandler: PurchaseHandler

View file

@ -28,8 +28,10 @@ class GiftSubscriptionActivity : PurchaseActivity() {
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var appConfigManager: AppConfigManager
@Inject
lateinit var purchaseHandler: PurchaseHandler

View file

@ -52,7 +52,6 @@ class GroupInviteActivity : BaseActivity() {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_send_invites -> {
setResult(Activity.RESULT_OK, createResultIntent())

View file

@ -49,6 +49,8 @@ class GuidelinesActivity : BaseActivity() {
return if (item.itemId == android.R.id.home) {
onBackPressed()
true
} else super.onOptionsItemSelected(item)
} else {
super.onOptionsItemSelected(item)
}
}
}

View file

@ -26,6 +26,7 @@ import javax.inject.Inject
class IntroActivity : BaseActivity(), View.OnClickListener, ViewPager.OnPageChangeListener {
private lateinit var binding: ActivityIntroBinding
@Inject
lateinit var contentRepository: ContentRepository

View file

@ -57,12 +57,15 @@ class LoginActivity : BaseActivity() {
@Inject
lateinit var apiClient: ApiClient
@Inject
lateinit var sharedPrefs: SharedPreferences
@Inject
lateinit var configManager: AppConfigManager
@Inject
lateinit var viewModel : AuthenticationViewModel
lateinit var viewModel: AuthenticationViewModel
private var isRegistering: Boolean = false
private var isShowingForm: Boolean = false
@ -171,7 +174,8 @@ class LoginActivity : BaseActivity() {
binding.forgotPassword.setOnClickListener { onForgotPasswordClicked() }
binding.googleLoginButton.setOnClickListener {
binding.googleLoginProgress.visibility = View.VISIBLE
viewModel.handleGoogleLogin(this, pickAccountResult) }
viewModel.handleGoogleLogin(this, pickAccountResult)
}
}
override fun loadTheme(sharedPreferences: SharedPreferences, forced: Boolean) {

View file

@ -41,8 +41,10 @@ import com.habitrpg.android.habitica.extensions.hideKeyboard
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.extensions.setScaledPadding
import com.habitrpg.android.habitica.extensions.updateStatusBarColor
import com.habitrpg.android.habitica.helpers.AmplitudeManager
import com.habitrpg.android.habitica.helpers.Analytics
import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.EventCategory
import com.habitrpg.android.habitica.helpers.HitType
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationOpenHandler
import com.habitrpg.android.habitica.helpers.SoundManager
@ -157,6 +159,13 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
}
}
private val classSelectionResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
userRepository.retrieveUser(true, true)
}
}
val isAppBarExpanded: Boolean
get() = binding.content.appbar.height - binding.content.appbar.bottom == 0
@ -265,19 +274,36 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
binding.content.headerView.setContent {
HabiticaTheme {
val user by viewModel.user.observeAsState(null)
val teamPlan by viewModel.userViewModel.currentTeamPlan.collectAsStateLifecycleAware(null)
val teamPlan by viewModel.userViewModel.currentTeamPlan.collectAsStateLifecycleAware(
null
)
val teamPlanMembers by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()
val canShowTeamHeader: Boolean by viewModel.canShowTeamPlanHeader
AppHeaderView(user, teamPlan = if (canShowTeamHeader) teamPlan else null, teamPlanMembers = teamPlanMembers) {
showAsBottomSheet { onClose ->
val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState(null)
val members by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()
GroupPlanMemberList(members, group) {
onClose()
FullProfileActivity.open(it)
AppHeaderView(
user,
teamPlan = if (canShowTeamHeader) teamPlan else null,
teamPlanMembers = teamPlanMembers,
onMemberRowClicked = {
showAsBottomSheet { onClose ->
val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState(
null
)
val members by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()
GroupPlanMemberList(members, group) {
onClose()
FullProfileActivity.open(it)
}
}
},
onClassSelectionClicked = {
val bundle = Bundle()
val isClassSelected = user?.flags?.classSelected ?: false
bundle.putBoolean("isInitialSelection", isClassSelected)
val intent = Intent(this@MainActivity, ClassSelectionActivity::class.java)
intent.putExtras(bundle)
classSelectionResult.launch(intent)
}
}
)
}
}
@ -356,7 +382,9 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
MainNavigationController.navigateBack()
}
true
} else super.onOptionsItemSelected(item)
} else {
super.onOptionsItemSelected(item)
}
}
override fun onResume() {
@ -404,10 +432,10 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
if (intent.hasExtra("sendAnalytics")) {
val additionalData = HashMap<String, Any>()
additionalData["identifier"] = identifier
AmplitudeManager.sendEvent(
Analytics.sendEvent(
"open notification",
AmplitudeManager.EVENT_CATEGORY_BEHAVIOUR,
AmplitudeManager.EVENT_HITTYPE_EVENT,
EventCategory.BEHAVIOUR,
HitType.EVENT,
additionalData
)
}
@ -681,7 +709,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
}
}
fun updateToolbarInteractivity(titleInteractive : Boolean) {
fun updateToolbarInteractivity(titleInteractive: Boolean) {
viewModel.canShowTeamPlanHeader.value = titleInteractive
binding.content.toolbarTitle.background?.alpha = if (titleInteractive) 255 else 0
if (titleInteractive) {

View file

@ -19,6 +19,7 @@ import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.SocialRepository
import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding
import com.habitrpg.android.habitica.extensions.fadeInAnimation
import com.habitrpg.android.habitica.extensions.observeOnce
import com.habitrpg.android.habitica.models.inventory.QuestContent
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
import com.habitrpg.common.habitica.extensions.fromHtml
@ -48,15 +49,17 @@ import javax.inject.Inject
class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener {
private lateinit var binding: ActivityNotificationsBinding
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var socialRepository: SocialRepository
val viewModel: NotificationsViewModel by viewModels()
var inflater: LayoutInflater? = null
val viewTagMap = mutableMapOf<String, View>()
var userLvl: Int? = null
override fun getLayoutResId(): Int = R.layout.activity_notifications
@ -72,6 +75,12 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
setupToolbar(binding.toolbar)
// Check user level to handle if a user loses hp and drops below necessary level to allocate points -
// and if so, don't display the notification to allocate points.
viewModel.user.observeOnce(this) { user ->
userLvl = user?.stats?.lvl ?: 0
}
inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as? LayoutInflater
lifecycleScope.launchCatching {
@ -146,12 +155,11 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
viewList.add(item)
}
}
refreshViews(viewList)
updateNotificationsAndRefresh(viewList)
}
}
private fun refreshViews(newItems: List<View>) {
private fun updateNotificationsAndRefresh(newItems: List<View>) {
val currentViews = (0 until binding.notificationItems.childCount).map {
binding.notificationItems.getChildAt(it)
}
@ -171,6 +179,17 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
}
}
private fun removeNotificationAndRefresh(notification: Notification) {
// Immediately remove notification for better user experience
// (To avoid waiting for the server to respond for potential slower connections)
this.notifications = this.notifications.filter { it.id != notification.id }
if (notifications.isEmpty()) {
displayNoNotificationsView()
} else {
displayNotificationsListView(notifications)
}
}
private fun createNotificationsHeaderView(notificationCount: Int): View? {
val header = inflater?.inflate(R.layout.notifications_header, binding.notificationItems, false)
@ -203,29 +222,38 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
)
}
private fun createNewStuffNotification(notification: Notification): View? {
private suspend fun createNewStuffNotification(notification: Notification): View? = withContext(ExceptionHandler.coroutine()) {
var baileyNotification = notification
val data = notification.data as? NewStuffData
val text = if (data?.title != null) {
fromHtml("<b>" + getString(R.string.new_bailey_update) + "</b><br>" + data.title)
} else {
fromHtml("<b>" + getString(R.string.new_bailey_update) + "</b>")
baileyNotification = userRepository.getNewsNotification() ?: return@withContext null
val baileyNewsData = baileyNotification.data as? NewStuffData
fromHtml("<b>" + getString(R.string.new_bailey_update) + "</b><br>" + baileyNewsData?.title)
}
baileyNotification.id = notification.id
return createDismissableNotificationItem(
notification,
return@withContext createDismissableNotificationItem(
baileyNotification,
text,
R.drawable.notifications_bailey
)
}
private fun createUnallocatedStatsNotification(notification: Notification): View? {
val data = notification.data as? UnallocatedPointsData
val level = userLvl ?: return null
return if (level >= 10) {
val data = notification.data as? UnallocatedPointsData
return createDismissableNotificationItem(
notification,
fromHtml(getString(R.string.unallocated_stats_points, data?.points.toString())),
R.drawable.notification_stat_sparkles
)
createDismissableNotificationItem(
notification,
fromHtml(getString(R.string.unallocated_stats_points, data?.points.toString())),
R.drawable.notification_stat_sparkles
)
} else {
null
}
}
private fun createMysteryItemsNotification(notification: Notification): View? {
@ -304,7 +332,10 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
}
val dismissButton = item?.findViewById(R.id.dismiss_button) as? ImageView
dismissButton?.setOnClickListener { viewModel.dismissNotification(notification) }
dismissButton?.setOnClickListener {
removeNotificationAndRefresh(notification)
viewModel.dismissNotification(notification)
}
val messageTextView = item?.findViewById(R.id.message_text) as? TextView
messageTextView?.text = messageText
@ -327,7 +358,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget
return item
}
private suspend fun createPartyInvitationNotification(notification: Notification): View? = withContext(ExceptionHandler.coroutine()) {
val data = notification.data as? PartyInvitationData
val inviterId = data?.invitation?.inviter

View file

@ -46,8 +46,10 @@ class SetupActivity : BaseActivity(), ViewPager.OnPageChangeListener {
@Inject
lateinit var apiClient: ApiClient
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var taskRepository: TaskRepository

View file

@ -27,6 +27,7 @@ class SkillMemberActivity : BaseActivity() {
@Inject
lateinit var socialRepository: SocialRepository
@Inject
lateinit var userViewModel: MainUserViewModel

View file

@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.databinding.ActivitySkillTasksBinding
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.android.habitica.modules.AppModule
import com.habitrpg.android.habitica.ui.fragments.skills.SkillTasksRecyclerViewFragment
import com.habitrpg.shared.habitica.models.tasks.TaskType
import dagger.hilt.android.AndroidEntryPoint
@ -87,6 +86,8 @@ class SkillTasksActivity : BaseActivity() {
return if (item.itemId == android.R.id.home) {
onBackPressed()
true
} else super.onOptionsItemSelected(item)
} else {
super.onOptionsItemSelected(item)
}
}
}

View file

@ -249,7 +249,6 @@ class TaskFormActivity : BaseActivity() {
}
}
title = ""
when {
taskId != null -> {
@ -365,7 +364,8 @@ class TaskFormActivity : BaseActivity() {
LabeledValue(getString(R.string.weekly), HabitResetOption.WEEKLY),
LabeledValue(getString(R.string.monthly), HabitResetOption.MONTHLY)
),
{ viewModel.habitResetOption.value = it }, columnSize = 3
{ viewModel.habitResetOption.value = it },
columnSize = 3
)
}
}
@ -648,10 +648,14 @@ class TaskFormActivity : BaseActivity() {
thisTask.up = viewModel.habitScoringPositive.value
thisTask.down = viewModel.habitScoringNegative.value
thisTask.frequency = viewModel.habitResetOption.value.value
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) thisTask.counterUp =
binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustNegativeStreakView.text?.isNotEmpty() == true) thisTask.counterDown =
binding.habitAdjustNegativeStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) {
thisTask.counterUp =
binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
}
if (binding.habitAdjustNegativeStreakView.text?.isNotEmpty() == true) {
thisTask.counterDown =
binding.habitAdjustNegativeStreakView.text.toString().toIntCatchOverflow()
}
} else if (taskType == TaskType.DAILY) {
thisTask.startDate = binding.taskSchedulingControls.startDate
thisTask.everyX = binding.taskSchedulingControls.everyX
@ -659,8 +663,10 @@ class TaskFormActivity : BaseActivity() {
thisTask.repeat = binding.taskSchedulingControls.weeklyRepeat
thisTask.setDaysOfMonth(binding.taskSchedulingControls.daysOfMonth)
thisTask.setWeeksOfMonth(binding.taskSchedulingControls.weeksOfMonth)
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) thisTask.streak =
binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
if (binding.habitAdjustPositiveStreakView.text?.isNotEmpty() == true) {
thisTask.streak =
binding.habitAdjustPositiveStreakView.text.toString().toIntCatchOverflow()
}
checkIfShowNotifLayout()
} else if (taskType == TaskType.TODO) {
thisTask.dueDate = binding.taskSchedulingControls.dueDate

View file

@ -67,27 +67,27 @@ import javax.inject.Inject
@HiltViewModel
class TaskSummaryViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
userRepository : UserRepository,
userViewModel : MainUserViewModel,
val taskRepository : TaskRepository,
val socialRepository : SocialRepository
userRepository: UserRepository,
userViewModel: MainUserViewModel,
val taskRepository: TaskRepository,
val socialRepository: SocialRepository
) : BaseViewModel(userRepository, userViewModel) {
val taskID: String = savedStateHandle[TaskFormActivity.TASK_ID_KEY] ?: ""
val task = taskRepository.getTask(taskID).asLiveData()
fun getMember(userID : String?) : Flow<Member?> {
fun getMember(userID: String?): Flow<Member?> {
return socialRepository.getMember(userID)
}
}
@AndroidEntryPoint
class TaskSummaryActivity : BaseActivity() {
override fun getLayoutResId() : Int? = null
override fun getLayoutResId(): Int? = null
private val viewModel : TaskSummaryViewModel by viewModels()
private val viewModel: TaskSummaryViewModel by viewModels()
override fun onCreate(savedInstanceState : Bundle?) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HabiticaTheme {
@ -98,7 +98,7 @@ class TaskSummaryActivity : BaseActivity() {
}
@Composable
fun TaskSummaryView(viewModel : TaskSummaryViewModel) {
fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
val taskDescriptionBuilder = TaskDescriptionBuilder(LocalContext.current)
val task by viewModel.task.observeAsState()
val titleModifier = Modifier.padding(top = 30.dp)
@ -107,9 +107,13 @@ fun TaskSummaryView(viewModel : TaskSummaryViewModel) {
if (task != null) {
val darkestColor = HabiticaTheme.colors.textPrimaryFor(task)
val topTextColor = if ((task?.value ?: 0.0) >= -20) colorResource(
task?.extraDarkTaskColor ?: R.color.white
) else Color.White
val topTextColor = if ((task?.value ?: 0.0) >= -20) {
colorResource(
task?.extraDarkTaskColor ?: R.color.white
)
} else {
Color.White
}
val systemUiController = rememberSystemUiController()
val statusBarColor = HabiticaTheme.colors.primaryBackgroundFor(task)
val lightestColor = HabiticaTheme.colors.contentBackgroundFor(task)
@ -169,8 +173,11 @@ fun TaskSummaryView(viewModel : TaskSummaryViewModel) {
modifier = titleModifier
)
Text(
task?.text ?: "", fontSize = 16.sp, color = darkestColor,
fontWeight = FontWeight.Normal, modifier = textModifier
task?.text ?: "",
fontSize = 16.sp,
color = darkestColor,
fontWeight = FontWeight.Normal,
modifier = textModifier
)
if (task?.notes?.isNotBlank() == true) {
Text(
@ -270,7 +277,8 @@ fun TaskSummaryView(viewModel : TaskSummaryViewModel) {
for (item in task?.group?.assignedUsersDetail ?: emptyList()) {
val member = viewModel.getMember(item.assignedUserID).collectAsState(null)
UserRow(
item.assignedUsername ?: "", member.value,
item.assignedUsername ?: "",
member.value,
Modifier
.padding(vertical = 4.dp)
.background(
@ -281,11 +289,15 @@ fun TaskSummaryView(viewModel : TaskSummaryViewModel) {
.heightIn(min = 24.dp)
.fillMaxWidth(),
color = darkestColor,
extraContent = if (item.completed) (
{
CompletedAt(item.completedDate)
}
) else null
extraContent = if (item.completed) {
(
{
CompletedAt(item.completedDate)
}
)
} else {
null
}
)
}
task?.group?.assignedUsersDetail?.find { it.assignedUserID == viewModel.userViewModel.userID }
@ -311,7 +323,7 @@ fun TaskSummaryView(viewModel : TaskSummaryViewModel) {
}
}
private fun String.makeBoldComposable() : AnnotatedString {
private fun String.makeBoldComposable(): AnnotatedString {
return buildAnnotatedString {
var isBold = false
for (segment in split("**")) {

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