mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 19:56:32 +00:00
Begin implementing new header
This commit is contained in:
parent
616b77cf0a
commit
bf5bb9939b
32 changed files with 424 additions and 95 deletions
|
|
@ -98,7 +98,7 @@ dependencies {
|
|||
implementation 'com.google.firebase:firebase-messaging-ktx'
|
||||
implementation 'com.google.firebase:firebase-config-ktx'
|
||||
implementation 'com.google.firebase:firebase-perf-ktx'
|
||||
implementation 'com.google.android.gms:play-services-ads:21.1.0'
|
||||
implementation 'com.google.android.gms:play-services-ads:21.2.0'
|
||||
implementation "com.google.android.gms:play-services-auth:$play_auth_version"
|
||||
implementation 'com.google.android.flexbox:flexbox:3.0.0'
|
||||
implementation "com.google.android.gms:play-services-wearable:$play_wearables_version"
|
||||
|
|
@ -107,12 +107,20 @@ dependencies {
|
|||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||
implementation "androidx.paging:paging-runtime-ktx:3.1.1"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
implementation "com.google.android.material:compose-theme-adapter:1.1.18"
|
||||
|
||||
implementation 'androidx.activity:activity-compose:1.5.1'
|
||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||
implementation "androidx.compose.material:material:$compose_version"
|
||||
implementation "androidx.compose.animation:animation:$compose_version"
|
||||
implementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
|
||||
|
||||
implementation 'com.willowtreeapps:signinwithapplebutton:0.3'
|
||||
|
||||
|
|
@ -160,6 +168,11 @@ android {
|
|||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
compose true
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.1"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
|
|
|
|||
|
|
@ -28,16 +28,15 @@
|
|||
app:expandedTitleMarginStart="0dp"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
<include
|
||||
android:id="@+id/avatar_with_bars"
|
||||
layout="@layout/avatar_with_bars"
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/header_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:layout_marginEnd="@dimen/header_border_spacing"
|
||||
android:layout_marginStart="@dimen/header_border_spacing"
|
||||
android:layout_marginBottom="@dimen/spacing_medium"
|
||||
app:layout_collapseMode="parallax" />
|
||||
app:layout_collapseMode="parallax"/>
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@
|
|||
android:contentDescription="@string/gems"
|
||||
android:layout_marginTop="20dp"/>
|
||||
|
||||
<com.habitrpg.android.habitica.ui.views.SparkView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -68,4 +68,5 @@
|
|||
|
||||
|
||||
<color name="widget_background">#2B203A</color>
|
||||
<color name="text_gold">@color/yellow_100</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -119,4 +119,5 @@
|
|||
<color name="lightly_tinted_background">@color/brand_700</color>
|
||||
<color name="dialog_background">@color/white</color>
|
||||
<color name="error_banner_background">@color/maroon_5</color>
|
||||
<color name="text_gold">@color/yellow_1</color>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -137,9 +137,7 @@ class AppConfigManager(contentRepository: ContentRepository?): com.habitrpg.comm
|
|||
}
|
||||
|
||||
fun enableTeamBoards(): Boolean {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return remoteConfig.getBoolean("enableTeamBoards")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,18 +26,15 @@ import com.habitrpg.android.habitica.R
|
|||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.extensions.addOkButton
|
||||
import com.habitrpg.android.habitica.extensions.subscribeWithErrorHandler
|
||||
import com.habitrpg.common.habitica.models.IAPGift
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.Transaction
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.proxy.AnalyticsManager
|
||||
import com.habitrpg.android.habitica.ui.activities.PurchaseActivity
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import com.habitrpg.common.habitica.models.IAPGift
|
||||
import com.habitrpg.common.habitica.models.PurchaseValidationRequest
|
||||
import com.habitrpg.common.habitica.models.Transaction
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -45,6 +42,9 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import retrofit2.HttpException
|
||||
import java.util.Date
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
class PurchaseHandler(
|
||||
private val context: Context,
|
||||
|
|
@ -186,7 +186,8 @@ class PurchaseHandler(
|
|||
return skuDetailsResult.skuDetailsList
|
||||
}
|
||||
|
||||
fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null) {
|
||||
fun purchase(activity: Activity, skuDetails: SkuDetails, recipient: String? = null, isSaleGemPurchase: Boolean = false) {
|
||||
this.isSaleGemPurchase = isSaleGemPurchase
|
||||
recipient?.let {
|
||||
addGift(skuDetails.sku, it)
|
||||
}
|
||||
|
|
@ -346,13 +347,26 @@ class PurchaseHandler(
|
|||
}
|
||||
}
|
||||
|
||||
private var isSaleGemPurchase = false
|
||||
|
||||
private fun gemAmountString(sku: String): String {
|
||||
return when (sku) {
|
||||
PurchaseTypes.Purchase4Gems -> "4"
|
||||
PurchaseTypes.Purchase21Gems -> "21"
|
||||
PurchaseTypes.Purchase42Gems -> "42"
|
||||
PurchaseTypes.Purchase84Gems -> "84"
|
||||
else -> ""
|
||||
if (isSaleGemPurchase) {
|
||||
isSaleGemPurchase = false
|
||||
return when (sku) {
|
||||
PurchaseTypes.Purchase4Gems -> "5"
|
||||
PurchaseTypes.Purchase21Gems -> "30"
|
||||
PurchaseTypes.Purchase42Gems -> "60"
|
||||
PurchaseTypes.Purchase84Gems -> "125"
|
||||
else -> ""
|
||||
}
|
||||
} else {
|
||||
return when (sku) {
|
||||
PurchaseTypes.Purchase4Gems -> "4"
|
||||
PurchaseTypes.Purchase21Gems -> "21"
|
||||
PurchaseTypes.Purchase42Gems -> "42"
|
||||
PurchaseTypes.Purchase84Gems -> "84"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.habitrpg.android.habitica.models.user
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.models.BaseObject
|
||||
|
|
@ -50,13 +50,13 @@ open class Stats : RealmObject(), AvatarStats, BaseObject {
|
|||
}
|
||||
}
|
||||
|
||||
fun getTranslatedClassName(context: Context): String {
|
||||
fun getTranslatedClassName(resources: Resources): String {
|
||||
return when (habitClass) {
|
||||
HEALER -> context.getString(R.string.healer)
|
||||
ROGUE -> context.getString(R.string.rogue)
|
||||
WARRIOR -> context.getString(R.string.warrior)
|
||||
MAGE -> context.getString(R.string.mage)
|
||||
else -> context.getString(R.string.warrior)
|
||||
HEALER -> resources.getString(R.string.healer)
|
||||
ROGUE -> resources.getString(R.string.rogue)
|
||||
WARRIOR -> resources.getString(R.string.warrior)
|
||||
MAGE -> resources.getString(R.string.mage)
|
||||
else -> resources.getString(R.string.warrior)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class UserModule {
|
|||
|
||||
@Provides
|
||||
@UserScope
|
||||
MainUserViewModel providesUserViewModel(UserRepository userRepository) {
|
||||
return new MainUserViewModel(userRepository);
|
||||
MainUserViewModel providesUserViewModel(String userID, UserRepository userRepository) {
|
||||
return new MainUserViewModel(userID, userRepository);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class AvatarWithBarsViewModel(
|
|||
binding.avatarView.setAvatar(user)
|
||||
|
||||
if (stats.habitClass != null && stats is Stats) {
|
||||
userClass = stats.getTranslatedClassName(context)
|
||||
userClass = stats.getTranslatedClassName(context.resources)
|
||||
}
|
||||
|
||||
binding.mpBar.visibility = if (stats.habitClass == null || (stats.lvl
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import androidx.navigation.NavDestination
|
|||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import com.google.android.gms.wearable.Wearable
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import com.google.firebase.perf.FirebasePerformance
|
||||
import com.habitrpg.android.habitica.BuildConfig
|
||||
import com.habitrpg.android.habitica.R
|
||||
|
|
@ -29,7 +30,6 @@ import com.habitrpg.android.habitica.components.UserComponent
|
|||
import com.habitrpg.android.habitica.data.ApiClient
|
||||
import com.habitrpg.android.habitica.data.InventoryRepository
|
||||
import com.habitrpg.android.habitica.data.TaskRepository
|
||||
import com.habitrpg.android.habitica.models.user.UserQuestStatus
|
||||
import com.habitrpg.android.habitica.databinding.ActivityMainBinding
|
||||
import com.habitrpg.android.habitica.extensions.hideKeyboard
|
||||
import com.habitrpg.android.habitica.extensions.observeOnce
|
||||
|
|
@ -46,11 +46,12 @@ import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase
|
|||
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
|
||||
import com.habitrpg.android.habitica.models.TutorialStep
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel
|
||||
import com.habitrpg.android.habitica.models.user.UserQuestStatus
|
||||
import com.habitrpg.android.habitica.ui.TutorialView
|
||||
import com.habitrpg.android.habitica.ui.fragments.NavigationDrawerFragment
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainActivityViewModel
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel
|
||||
import com.habitrpg.android.habitica.ui.views.AppHeaderView
|
||||
import com.habitrpg.android.habitica.ui.views.SnackbarActivity
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.QuestCompletedDialog
|
||||
import com.habitrpg.android.habitica.ui.views.yesterdailies.YesterdailyDialog
|
||||
|
|
@ -61,9 +62,9 @@ import com.habitrpg.android.habitica.widget.TodoListWidgetProvider
|
|||
import com.habitrpg.common.habitica.extensions.dpToPx
|
||||
import com.habitrpg.common.habitica.extensions.getThemeColor
|
||||
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
||||
import com.habitrpg.common.habitica.views.AvatarView
|
||||
import com.habitrpg.shared.habitica.models.responses.MaintenanceResponse
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
|
||||
import com.habitrpg.common.habitica.views.AvatarView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -100,7 +101,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
val snackbarContainer: ViewGroup
|
||||
get() = binding.content.snackbarContainer
|
||||
|
||||
private var avatarInHeader: AvatarWithBarsViewModel? = null
|
||||
val notificationsViewModel: NotificationsViewModel by viewModels()
|
||||
val viewModel: MainActivityViewModel by viewModels()
|
||||
private var sideAvatarView: AvatarView? = null
|
||||
|
|
@ -147,7 +147,6 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
|
||||
setupToolbar(binding.content.toolbar)
|
||||
|
||||
avatarInHeader = AvatarWithBarsViewModel(this, binding.content.avatarWithBars, viewModel.userViewModel)
|
||||
sideAvatarView = AvatarView(this, showBackground = true, showMount = false, showPet = false)
|
||||
|
||||
viewModel.user.observe(this) {
|
||||
|
|
@ -210,6 +209,12 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
|
|||
supportActionBar?.setHomeButtonEnabled(true)
|
||||
setupNotifications()
|
||||
setupBottomnavigationLayoutListener()
|
||||
|
||||
binding.content.headerView.setContent {
|
||||
MdcTheme(setTextColors = true) {
|
||||
AppHeaderView(viewModel.userViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.onCreate()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,11 +118,11 @@ abstract class BaseMainFragment<VB : ViewBinding> : BaseFragment<VB>() {
|
|||
}
|
||||
|
||||
private fun hideToolbar() {
|
||||
activity?.binding?.content?.avatarWithBars?.root?.visibility = View.GONE
|
||||
activity?.binding?.content?.headerView?.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun showToolbar() {
|
||||
activity?.binding?.content?.avatarWithBars?.root?.visibility = View.VISIBLE
|
||||
activity?.binding?.content?.headerView?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun disableToolbarScrolling() {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.habitrpg.android.habitica.components.UserComponent
|
|||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.android.habitica.databinding.FragmentGemPurchaseBinding
|
||||
import com.habitrpg.android.habitica.extensions.addCancelButton
|
||||
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
||||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.PurchaseHandler
|
||||
|
|
@ -25,11 +24,12 @@ import com.habitrpg.android.habitica.ui.fragments.BaseFragment
|
|||
import com.habitrpg.android.habitica.ui.fragments.PromoInfoFragment
|
||||
import com.habitrpg.android.habitica.ui.helpers.dismissKeyboard
|
||||
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
|
||||
import javax.inject.Inject
|
||||
import com.habitrpg.common.habitica.extensions.isUsingNightModeResources
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>() {
|
||||
|
||||
|
|
@ -50,6 +50,8 @@ class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>() {
|
|||
component.inject(this)
|
||||
}
|
||||
|
||||
private var isGemSaleHappening = false
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
@ -67,6 +69,7 @@ class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>() {
|
|||
val promo = appConfigManager.activePromo()
|
||||
if (promo != null) {
|
||||
binding?.let {
|
||||
isGemSaleHappening = true
|
||||
promo.configurePurchaseBanner(it)
|
||||
if (promo.promoType != PromoType.SUBSCRIPTION) {
|
||||
promo.configureGemView(it.gems4View.binding, 4)
|
||||
|
|
@ -122,7 +125,7 @@ class GemsPurchaseFragment : BaseFragment<FragmentGemPurchaseBinding>() {
|
|||
|
||||
private fun purchaseGems(view: GemPurchaseOptionsView?) {
|
||||
val identifier = view?.sku ?: return
|
||||
activity?.let { purchaseHandler.purchase(it, identifier) }
|
||||
activity?.let { purchaseHandler.purchase(it, identifier, null, isGemSaleHappening) }
|
||||
}
|
||||
|
||||
private fun showGiftGemsDialog() {
|
||||
|
|
|
|||
|
|
@ -422,6 +422,7 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
|
|||
if (viewModel.ownerTitle.isNotBlank()) {
|
||||
activity?.title = viewModel.ownerTitle
|
||||
}
|
||||
viewModel.userViewModel.currentTeamPlan = viewModel.teamPlans[viewModel.ownerID.value]
|
||||
val isPersonalBoard = viewModel.isPersonalBoard
|
||||
bottomNavigation?.canAddTasks = isPersonalBoard
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class GroupMemberViewHolder(itemView: View) : androidx.recyclerview.widget.Recyc
|
|||
binding.displayNameTextview.tier = user.contributor?.level ?: 0
|
||||
|
||||
if (user.hasClass) {
|
||||
binding.sublineTextview.text = itemView.context.getString(R.string.user_level_with_class, user.stats?.lvl, user.stats?.getTranslatedClassName(itemView.context))
|
||||
binding.sublineTextview.text = itemView.context.getString(R.string.user_level_with_class, user.stats?.lvl, user.stats?.getTranslatedClassName(itemView.context.resources))
|
||||
} else {
|
||||
binding.sublineTextview.text = itemView.context.getString(R.string.user_level, user.stats?.lvl)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,28 @@ package com.habitrpg.android.habitica.ui.viewmodels
|
|||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.habitrpg.android.habitica.HabiticaBaseApplication
|
||||
import com.habitrpg.android.habitica.data.SocialRepository
|
||||
import com.habitrpg.android.habitica.data.UserRepository
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.TeamPlan
|
||||
import com.habitrpg.android.habitica.models.invitations.PartyInvite
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.modules.AppModule
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
|
||||
class MainUserViewModel(val userRepository: UserRepository) {
|
||||
class MainUserViewModel(private val providedUserID: String, val userRepository: UserRepository, val socialRepository: SocialRepository) {
|
||||
|
||||
@field:[Inject Named(AppModule.NAMED_USER_ID)]
|
||||
lateinit var injectedUserID: String
|
||||
|
||||
val formattedUsername: CharSequence?
|
||||
get() = user.value?.formattedUsername
|
||||
val partyInvitations: List<PartyInvite>
|
||||
get() = user.value?.invitations?.parties ?: emptyList()
|
||||
val userID: String
|
||||
get() = user.value?.id ?: injectedUserID
|
||||
get() = user.value?.id ?: providedUserID
|
||||
val username: CharSequence
|
||||
get() = user.value?.username ?: ""
|
||||
val displayName: CharSequence
|
||||
|
|
@ -36,12 +37,13 @@ class MainUserViewModel(val userRepository: UserRepository) {
|
|||
val mirrorGroupTasks: List<String>
|
||||
get() = user.value?.preferences?.tasks?.mirrorGroupTasks ?: emptyList()
|
||||
|
||||
val user: LiveData<User?>
|
||||
|
||||
init {
|
||||
HabiticaBaseApplication.userComponent?.inject(this)
|
||||
user = userRepository.getUser().asLiveData()
|
||||
}
|
||||
val user: LiveData<User?> = userRepository.getUser().asLiveData()
|
||||
var currentTeamPlan: MutableStateFlow<TeamPlan?> = MutableStateFlow(null)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
var currentTeamPlanGroup = currentTeamPlan
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged { old, new -> old.id == new.id }
|
||||
.flatMapLatest { socialRepository.getGroup(it.id) }
|
||||
|
||||
fun onCleared() {
|
||||
userRepository.close()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.habitrpg.android.habitica.data.TaskRepository
|
|||
import com.habitrpg.android.habitica.helpers.AmplitudeManager
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.RxErrorHandler
|
||||
import com.habitrpg.android.habitica.models.TeamPlan
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskDirection
|
||||
import com.habitrpg.shared.habitica.models.responses.TaskScoringResult
|
||||
|
|
@ -45,6 +46,7 @@ class TasksViewModel : BaseViewModel() {
|
|||
val ownerID: MutableLiveData<String?> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
var teamPlans = mapOf<String, TeamPlan>()
|
||||
var initialPreferenceFilterSet: Boolean = false
|
||||
|
||||
val isPersonalBoard: Boolean
|
||||
|
|
@ -60,8 +62,9 @@ class TasksViewModel : BaseViewModel() {
|
|||
if (appConfigManager.enableTeamBoards()) {
|
||||
viewModelScope.launch {
|
||||
userRepository.getTeamPlans()
|
||||
.collect {
|
||||
owners = listOf(Pair(userViewModel.userID, userViewModel.displayName)) + it.map {
|
||||
.collect { plans ->
|
||||
teamPlans = plans.associateBy { it.id }
|
||||
owners = listOf(Pair(userViewModel.userID, userViewModel.displayName)) + plans.map {
|
||||
Pair(
|
||||
it.id,
|
||||
it.summary
|
||||
|
|
|
|||
|
|
@ -0,0 +1,266 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.LinearProgressIndicator
|
||||
import androidx.compose.material.ProgressIndicatorDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.models.user.User
|
||||
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
|
||||
import com.habitrpg.common.habitica.helpers.NumberAbbreviator
|
||||
import java.math.RoundingMode
|
||||
import java.text.NumberFormat
|
||||
|
||||
@Composable
|
||||
fun UserLevelText(user: User) {
|
||||
val text = if (user.hasClass) {
|
||||
stringResource(
|
||||
id = R.string.user_level_with_class,
|
||||
user.stats?.lvl ?: 0,
|
||||
user.stats?.getTranslatedClassName(
|
||||
LocalContext.current.resources
|
||||
) ?: ""
|
||||
)
|
||||
} else {
|
||||
stringResource(id = R.string.user_level, user.stats?.lvl ?: 0)
|
||||
}
|
||||
Text(
|
||||
text,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CurrencyText(
|
||||
currency: String,
|
||||
value: Double,
|
||||
modifier: Modifier = Modifier,
|
||||
decimals: Int = 2,
|
||||
minForAbbrevation: Int = 0
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
when (currency) {
|
||||
"gold" -> HabiticaIconsHelper.imageOfGold()
|
||||
"gems" -> HabiticaIconsHelper.imageOfGem()
|
||||
"hourglasses" -> HabiticaIconsHelper.imageOfHourglass()
|
||||
else -> null
|
||||
}?.asImageBitmap()?.let { Image(it, null, Modifier.padding(end = 5.dp)) }
|
||||
Text(
|
||||
NumberAbbreviator.abbreviate(null, value, decimals, minForAbbrevation),
|
||||
color = when (currency) {
|
||||
"gold" -> colorResource(R.color.text_gold)
|
||||
"gems" -> colorResource(R.color.text_green)
|
||||
"hourglasses" -> colorResource(R.color.text_brand)
|
||||
else -> colorResource(R.color.text_primary)
|
||||
},
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AppHeaderView(
|
||||
viewModel: MainUserViewModel,
|
||||
) {
|
||||
val user by viewModel.user.observeAsState(null)
|
||||
val displayedTeamPlan = viewModel.currentTeamPlan
|
||||
Column {
|
||||
Row {
|
||||
ComposableAvatarView(
|
||||
user,
|
||||
Modifier
|
||||
.size(110.dp, 100.dp)
|
||||
.padding(end = 16.dp)
|
||||
)
|
||||
Column(modifier = Modifier.height(100.dp)) {
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
LabeledBar(
|
||||
icon = HabiticaIconsHelper.imageOfHeartLightBg(),
|
||||
label = stringResource(R.string.HP_default),
|
||||
color = colorResource(R.color.hpColor),
|
||||
value = user?.stats?.hp ?: 0.0,
|
||||
maxValue = user?.stats?.maxHealth?.toDouble() ?: 0.0,
|
||||
displayCompact = displayedTeamPlan != null,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
LabeledBar(
|
||||
icon = HabiticaIconsHelper.imageOfExperience(),
|
||||
label = stringResource(R.string.XP_default),
|
||||
color = colorResource(R.color.xpColor),
|
||||
value = user?.stats?.exp ?: 0.0,
|
||||
maxValue = user?.stats?.toNextLevel?.toDouble() ?: 0.0,
|
||||
displayCompact = displayedTeamPlan != null,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
if (user?.hasClass == true) {
|
||||
LabeledBar(
|
||||
icon = HabiticaIconsHelper.imageOfMagic(),
|
||||
label = stringResource(R.string.MP_default),
|
||||
color = colorResource(R.color.mpColor),
|
||||
value = user?.stats?.mp ?: 0.0,
|
||||
maxValue = user?.stats?.maxMP?.toDouble() ?: 0.0,
|
||||
displayCompact = displayedTeamPlan != null,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
}
|
||||
}
|
||||
val animWidth = with(LocalDensity.current) { 48.dp.roundToPx() }
|
||||
AnimatedVisibility(
|
||||
visible = displayedTeamPlan != null,
|
||||
enter = slideInHorizontally { animWidth } + fadeIn(),
|
||||
exit = slideOutHorizontally { animWidth } + fadeOut()) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.height(72.dp)
|
||||
.width(48.dp)
|
||||
.padding(start = 12.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(
|
||||
colorResource(R.color.window_background)
|
||||
)
|
||||
) {
|
||||
Text("M")
|
||||
}
|
||||
}
|
||||
}
|
||||
val animHeight = with(LocalDensity.current) { 40.dp.roundToPx() }
|
||||
AnimatedVisibility(
|
||||
visible = displayedTeamPlan != null,
|
||||
enter = slideInVertically { animHeight } + fadeIn(),
|
||||
exit = slideOutVertically { animHeight } + fadeOut()) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp)
|
||||
.padding(top = 12.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(
|
||||
colorResource(R.color.window_background)
|
||||
)
|
||||
) {
|
||||
Text("A")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (user?.hasClass == true) {
|
||||
val icon = when (user?.stats?.habitClass) {
|
||||
"warrior" -> HabiticaIconsHelper.imageOfWarriorLightBg().asImageBitmap()
|
||||
"wizard" -> HabiticaIconsHelper.imageOfMageLightBg().asImageBitmap()
|
||||
"healer" -> HabiticaIconsHelper.imageOfHealerLightBg().asImageBitmap()
|
||||
"rogue" -> HabiticaIconsHelper.imageOfRogueLightBg().asImageBitmap()
|
||||
else -> null
|
||||
}
|
||||
if (icon != null) {
|
||||
Image(bitmap = icon, "", modifier = Modifier.padding(end = 4.dp))
|
||||
}
|
||||
}
|
||||
user?.let { UserLevelText(it) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
user?.hourglassCount?.toDouble()
|
||||
?.let { CurrencyText("hourglasses", it, modifier = Modifier.padding(end = 12.dp)) }
|
||||
CurrencyText("gold", user?.stats?.gp ?: 0.0, modifier = Modifier.padding(end = 12.dp))
|
||||
CurrencyText("gems", user?.gemCount?.toDouble() ?: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LabeledBar(
|
||||
icon: Bitmap,
|
||||
label: String,
|
||||
color: Color,
|
||||
value: Double,
|
||||
maxValue: Double,
|
||||
displayCompact: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val formatter = NumberFormat.getInstance()
|
||||
formatter.maximumFractionDigits = 1
|
||||
formatter.roundingMode = RoundingMode.UP
|
||||
formatter.isGroupingUsed = true
|
||||
|
||||
val animatedValue = animateFloatAsState(
|
||||
targetValue = value.toFloat(),
|
||||
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||
).value
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
|
||||
AnimatedVisibility(
|
||||
visible = !displayCompact,
|
||||
enter = slideInHorizontally { -18 },
|
||||
exit = slideOutHorizontally { -18 }) {
|
||||
Image(icon.asImageBitmap(), null, modifier = Modifier.padding(end = 8.dp))
|
||||
}
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
LinearProgressIndicator(
|
||||
progress = (animatedValue / maxValue).toFloat(),
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(CircleShape)
|
||||
.height(8.dp),
|
||||
backgroundColor = colorResource(R.color.window_background),
|
||||
color = color
|
||||
)
|
||||
AnimatedVisibility(visible = !displayCompact) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
"${formatter.format(animatedValue)} / ${formatter.format(maxValue)}",
|
||||
fontSize = 12.sp,
|
||||
color = colorResource(R.color.text_ternary)
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Text(label, fontSize = 12.sp, color = colorResource(R.color.text_ternary))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.habitrpg.android.habitica.ui.views
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.habitrpg.common.habitica.views.AvatarView
|
||||
import com.habitrpg.shared.habitica.models.Avatar
|
||||
|
||||
@Composable
|
||||
fun ComposableAvatarView(
|
||||
avatar: Avatar?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
AndroidView(
|
||||
modifier = modifier, // Occupy the max size in the Compose UI tree
|
||||
factory = { context ->
|
||||
AvatarView(context)
|
||||
},
|
||||
update = { view ->
|
||||
if (avatar != null) {
|
||||
view.setAvatar(avatar)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -69,21 +69,21 @@ class CurrencyView : androidx.appcompat.widget.AppCompatTextView {
|
|||
|
||||
private fun configureCurrency() {
|
||||
if ("gold" == currency) {
|
||||
icon = com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper.imageOfGold()
|
||||
icon = HabiticaIconsHelper.imageOfGold()
|
||||
if (lightBackground) {
|
||||
setTextColor(ContextCompat.getColor(context, R.color.yellow_1))
|
||||
} else {
|
||||
setTextColor(ContextCompat.getColor(context, R.color.yellow_100))
|
||||
}
|
||||
} else if ("gems" == currency) {
|
||||
icon = com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper.imageOfGem()
|
||||
icon = HabiticaIconsHelper.imageOfGem()
|
||||
if (lightBackground) {
|
||||
setTextColor(ContextCompat.getColor(context, R.color.green_10))
|
||||
} else {
|
||||
setTextColor(ContextCompat.getColor(context, R.color.green_50))
|
||||
}
|
||||
} else if ("hourglasses" == currency) {
|
||||
icon = com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper.imageOfHourglass()
|
||||
icon = HabiticaIconsHelper.imageOfHourglass()
|
||||
if (lightBackground) {
|
||||
setTextColor(ContextCompat.getColor(context, R.color.brand_300))
|
||||
} else {
|
||||
|
|
|
|||
24
build.gradle
24
build.gradle
|
|
@ -2,25 +2,27 @@
|
|||
|
||||
buildscript {
|
||||
ext {
|
||||
target_sdk = 32
|
||||
target_sdk = 33
|
||||
app_version_name = ''
|
||||
app_version_code = 0
|
||||
|
||||
|
||||
amplitude_version = '3.35.1'
|
||||
appcompat_version = '1.5.0'
|
||||
appcompat_version = '1.5.1'
|
||||
coil_version = '2.1.0'
|
||||
core_ktx_version = '1.8.0'
|
||||
coroutines_version = '1.6.2'
|
||||
compose_version = '1.2.1'
|
||||
core_ktx_version = '1.9.0'
|
||||
coroutines_version = '1.6.4'
|
||||
daggerhilt_version = '2.42'
|
||||
firebase_bom = '30.2.0'
|
||||
kotlin_version = '1.7.10'
|
||||
lifecycle_version = '2.5.0'
|
||||
lifecycle_version = '2.5.1'
|
||||
markwon_version = '4.6.2'
|
||||
moshi_version = '1.13.0'
|
||||
navigation_version = '2.5.2'
|
||||
okhttp_version = '4.9.3'
|
||||
play_wearables_version = '17.1.0'
|
||||
play_auth_version = '20.2.0'
|
||||
play_wearables_version = '18.0.0'
|
||||
play_auth_version = '20.3.0'
|
||||
preferences_version = '1.2.0'
|
||||
realm_version = '1.0.2'
|
||||
retrofit_version = '2.9.0'
|
||||
|
|
@ -33,15 +35,15 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.3'
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
classpath 'com.google.gms:google-services:4.3.13'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
classpath "io.realm:realm-gradle-plugin:10.11.0"
|
||||
classpath("io.realm.kotlin:gradle-plugin:$realm_version")
|
||||
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.1"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
|
||||
classpath 'com.google.firebase:perf-plugin:1.4.1'
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerhilt_version"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
namespace 'com.habitrpg.common.habitica'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.habitrpg.common.habitica">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
|
|
@ -7,7 +7,7 @@ import java.text.DecimalFormat
|
|||
|
||||
object NumberAbbreviator {
|
||||
|
||||
fun abbreviate(context: Context, number: Double, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
|
||||
fun abbreviate(context: Context?, number: Double, numberOfDecimals: Int = 2, minForAbbrevation: Int = 0): String {
|
||||
var usedNumber = number
|
||||
var counter = 0
|
||||
while (usedNumber >= 1000 && number >= minForAbbrevation) {
|
||||
|
|
@ -23,11 +23,11 @@ object NumberAbbreviator {
|
|||
return formatter.format(usedNumber)
|
||||
}
|
||||
|
||||
private fun abbreviationForCounter(context: Context, counter: Int): String = when (counter) {
|
||||
1 -> context.getString(R.string.thousand_abbrev)
|
||||
2 -> context.getString(R.string.million_abbrev)
|
||||
3 -> context.getString(R.string.billion_abbrev)
|
||||
4 -> context.getString(R.string.trillion_abbrev)
|
||||
private fun abbreviationForCounter(context: Context?, counter: Int): String = when (counter) {
|
||||
1 -> context?.getString(R.string.thousand_abbrev) ?: "k"
|
||||
2 -> context?.getString(R.string.million_abbrev) ?: "m"
|
||||
3 -> context?.getString(R.string.billion_abbrev) ?: "b"
|
||||
4 -> context?.getString(R.string.trillion_abbrev) ?: "t"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
<color name="xpColor">@color/yellow_100</color>
|
||||
<color name="mpColor">@color/blue_100</color>
|
||||
|
||||
<color name="text_primary">@color/gray_10</color>
|
||||
<color name="text_primary">@color/gray_50</color>
|
||||
<color name="text_secondary">@color/gray_100</color>
|
||||
<color name="text_ternary">@color/gray_200</color>
|
||||
<color name="text_quad">@color/gray_300</color>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Runs all the tests
|
|||
[bundle exec] fastlane android staffapk
|
||||
```
|
||||
|
||||
Build Staff APK for sara
|
||||
Build Staff APK
|
||||
|
||||
### android staff
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
New in 4.0.2:
|
||||
New in 4.0.3:
|
||||
-Habitica has a brand new WearOS app for smart watches!
|
||||
-Reminders for tasks done the previous day will show again
|
||||
-Group Plan subscribers can switch on displaying shared tasks from Settings
|
||||
-Past To Do reminders will not constantly show anymore
|
||||
-Pet category labels show again
|
||||
-Newly designed Backgrounds section
|
||||
-Ability to filter, preview, and pin Backgrounds
|
||||
-New bottom sheet designs in Items, Pets & Mounts, and Filters
|
||||
-New Day Start Adjustment interface
|
||||
-Improvements to payment and subscription handling
|
||||
|
||||
|
|
|
|||
|
|
@ -60,4 +60,5 @@ android {
|
|||
minSdk = 21
|
||||
targetSdk = 32
|
||||
}
|
||||
namespace = "com.habitrpg.shared.habitica"
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.habitrpg.shared.habitica" />
|
||||
<manifest />
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
NAME=4.0.3
|
||||
CODE=4521
|
||||
CODE=4561
|
||||
|
|
@ -88,6 +88,7 @@ android {
|
|||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
namespace 'com.habitrpg.android.habitica'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.habitrpg.android.habitica">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue