various fixes

This commit is contained in:
Phillip Thelen 2022-12-20 13:36:34 +01:00
parent 25fa75497e
commit e50f2f42be
30 changed files with 256 additions and 168 deletions

View file

@ -102,13 +102,13 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
implementation "androidx.fragment:fragment-ktx:1.5.4"
implementation "androidx.fragment:fragment-ktx:1.5.5"
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.21"
implementation "com.google.accompanist:accompanist-themeadapter-material:$accompanist_version"
implementation "androidx.compose.material3:material3:1.0.1"
implementation "com.google.accompanist:accompanist-systemuicontroller:0.27.1"
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"
implementation 'androidx.activity:activity-compose:1.6.1'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"

View file

@ -171,7 +171,8 @@
android:layout_height="wrap_content"
android:text="@string/months_subscribed"
android:gravity="center"
android:fontFamily="sans-serif-medium"/>
android:fontFamily="sans-serif-medium"
android:textColor="@color/text_secondary"/>
</LinearLayout>
<LinearLayout
@ -194,9 +195,10 @@
style="@style/subscriptionBoxCompactText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/months_subscribed"
android:text="@string/monthly_gem_cap"
android:gravity="center"
android:fontFamily="sans-serif-medium"/>
android:fontFamily="sans-serif-medium"
android:textColor="@color/text_secondary"/>
</LinearLayout>
</LinearLayout>

View file

@ -1286,6 +1286,7 @@
<string name="unmute_user">Unmute User</string>
<string name="status">Status</string>
<string name="regular_access">Regular Access</string>
<string name="message_flagged">Message flagged %d times.</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>
<item quantity="one">You, %d other</item>

View file

@ -105,7 +105,7 @@
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="colorBoxStroke">@color/red_10</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="toolbarContentColor">@color/red_100</item>
<item name="toolbarContentColor">@color/red_1</item>
<item name="colorTintedBackground">@color/red_00</item>
<item name="colorTintedBackgroundOffset">@color/red_0</item>
<item name="colorPrimaryText">@color/red_600</item>
@ -136,7 +136,7 @@
<item name="barColor">@color/gray_1</item>
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="toolbarContentColor">@color/maroon_100</item>
<item name="toolbarContentColor">@color/white</item>
<item name="colorTintedBackground">@color/maroon_00</item>
<item name="colorTintedBackgroundOffset">@color/maroon_0</item>
<item name="colorPrimaryText">@color/maroon_600</item>
@ -169,7 +169,7 @@
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="colorBoxStroke">@color/orange_10</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="toolbarContentColor">@color/orange_100</item>
<item name="toolbarContentColor">@color/orange_1</item>
<item name="colorTintedBackground">@color/orange_00</item>
<item name="colorTintedBackgroundOffset">@color/orange_0</item>
<item name="colorPrimaryText">@color/orange_600</item>
@ -202,7 +202,7 @@
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="colorBoxStroke">@color/yellow_5</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="toolbarContentColor">@color/yellow_100</item>
<item name="toolbarContentColor">@color/yellow_1</item>
<item name="colorTintedBackground">@color/yellow_00</item>
<item name="colorTintedBackgroundOffset">@color/yellow_0</item>
<item name="colorPrimaryText">@color/yellow_600</item>
@ -235,7 +235,7 @@
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="colorBoxStroke">@color/green_10</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="toolbarContentColor">@color/green_100</item>
<item name="toolbarContentColor">@color/green_1</item>
<item name="colorTintedBackground">@color/green_00</item>
<item name="colorTintedBackgroundOffset">@color/green_0</item>
<item name="colorPrimaryText">@color/green_600</item>
@ -268,7 +268,7 @@
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="colorBoxStroke">@color/teal_10</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="toolbarContentColor">@color/teal_100</item>
<item name="toolbarContentColor">@color/teal_1</item>
<item name="colorTintedBackground">@color/teal_00</item>
<item name="colorTintedBackgroundOffset">@color/teal_0</item>
<item name="colorPrimaryText">@color/teal_600</item>
@ -301,7 +301,7 @@
<item name="colorPrimaryOffset">@color/gray_10</item>
<item name="textColorPrimaryDark">@color/gray_200</item>
<item name="colorBoxStroke">@color/blue_10</item>
<item name="toolbarContentColor">@color/blue_100</item>
<item name="toolbarContentColor">@color/blue_1</item>
<item name="colorTintedBackground">@color/blue_00</item>
<item name="colorTintedBackgroundOffset">@color/blue_0</item>
<item name="colorPrimaryText">@color/blue_600</item>
@ -393,6 +393,7 @@
<style name="Toolbar.Modern" parent="ThemeOverlay.AppCompat.DayNight.ActionBar">
<item name="android:textColorPrimary">?attr/headerTextColor</item>
<item name="textColorSecondary">?attr/headerTextColor</item>
<item name="textColorPrimary">?attr/toolbarContentColor</item>
<item name="android:background">?attr/headerBackgroundColor</item>
<item name="actionMenuTextColor">?attr/toolbarContentColor</item>
<item name="android:actionMenuTextColor">?attr/toolbarContentColor</item>

View file

@ -36,6 +36,8 @@ open class UserStyles : RealmObject(), Avatar {
get() {
return false
}
override val id: String?
get() = null
override var balance: Double = 0.0
override var authentication: Authentication? = null
override var stats: Stats? = null

View file

@ -255,7 +255,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
this.value < 1 -> return R.color.yellow_0
this.value < 5 -> return R.color.green_0
this.value < 10 -> return R.color.teal_0
else -> R.color.blue_1
else -> R.color.blue_0
}
}
@ -268,7 +268,7 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
this.value < 1 -> return R.color.yellow_00
this.value < 5 -> return R.color.green_00
this.value < 10 -> return R.color.teal_00
else -> R.color.blue_1
else -> R.color.blue_00
}
}

View file

@ -1,8 +1,6 @@
package com.habitrpg.android.habitica.models.user
import android.content.res.Resources
import com.google.gson.annotations.SerializedName
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.BaseObject
import com.habitrpg.shared.habitica.models.AvatarStats
import io.realm.RealmObject
@ -50,16 +48,6 @@ open class Stats : RealmObject(), AvatarStats, BaseObject {
}
}
fun getTranslatedClassName(resources: Resources): String {
return when (habitClass) {
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)
}
}
fun merge(stats: Stats?) {
if (stats == null) {
return

View file

@ -45,7 +45,7 @@ open class User : RealmObject(), BaseMainObject, Avatar, VersionedObject {
@PrimaryKey
@SerializedName("_id")
var id: String? = null
override var id: String? = null
@SerializedName("_v")
override var versionNumber: Int = 0

View file

@ -12,6 +12,8 @@ import android.view.ViewGroup
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.MenuCompat
@ -34,12 +36,12 @@ import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.Outfit
import com.habitrpg.android.habitica.models.user.Permission
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.AvatarWithBarsViewModel
import com.habitrpg.android.habitica.ui.adapter.social.AchievementProfileAdapter
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.views.AppHeaderView
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar.SnackbarDisplayType
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.common.habitica.extensions.getThemeColor
import com.habitrpg.common.habitica.extensions.loadImage
import com.habitrpg.common.habitica.helpers.RecyclerViewState
import com.habitrpg.common.habitica.helpers.setMarkdown
@ -57,7 +59,7 @@ import kotlin.math.min
class FullProfileActivity : BaseActivity() {
private var blocks: List<String> = listOf()
private var isModerator = false
private var member: Member? = null
private var member: MutableState<Member?> = mutableStateOf(null)
@Inject
lateinit var inventoryRepository: InventoryRepository
@ -78,7 +80,6 @@ class FullProfileActivity : BaseActivity() {
private var attributeDetailsHidden = true
private val attributeRows = ArrayList<TableRow>()
private val dateFormatter = SimpleDateFormat.getDateInstance()
private var avatarWithBars: AvatarWithBarsViewModel? = null
private lateinit var binding: ActivityFullProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
@ -95,17 +96,13 @@ class FullProfileActivity : BaseActivity() {
lifecycleScope.launch(ExceptionHandler.coroutine()) {
refresh()
}
avatarWithBars = AvatarWithBarsViewModel(this, binding.avatarWithBars)
binding.avatarWithBars.root.setBackgroundColor(
ContextCompat.getColor(
this,
R.color.transparent
)
)
binding.avatarWithBars.hpBar.barBackgroundColor = getThemeColor(R.attr.colorWindowBackground)
binding.avatarWithBars.xpBar.barBackgroundColor = getThemeColor(R.attr.colorWindowBackground)
binding.avatarWithBars.mpBar.barBackgroundColor = getThemeColor(R.attr.colorWindowBackground)
binding.avatarWithBars.setContent {
HabiticaTheme {
AppHeaderView(member.value) {
}
}
}
attributeRows.clear()
binding.attributesCardView.setOnClickListener { toggleAttributeDetails() }
@ -137,7 +134,7 @@ class FullProfileActivity : BaseActivity() {
if (member != null) {
updateView(member)
}
this@FullProfileActivity.member = member
this@FullProfileActivity.member.value = member
}
invalidateOptionsMenu()
}
@ -149,7 +146,7 @@ class FullProfileActivity : BaseActivity() {
if (member != null) {
updateView(member)
}
this@FullProfileActivity.member = member
this@FullProfileActivity.member.value = member
}
override fun onDestroy() {
@ -169,17 +166,17 @@ class FullProfileActivity : BaseActivity() {
}
menu.setGroupVisible(R.id.admin_items, isModerator)
if (isModerator) {
menu.findItem(R.id.ban_user)?.title = getString(if (member?.authentication?.blocked == true) {
menu.findItem(R.id.ban_user)?.title = getString(if (member.value?.authentication?.blocked == true) {
R.string.unban_user
} else {
R.string.ban_user
})
menu.findItem(R.id.shadow_mute_user)?.title = getString(if (member?.flags?.chatShadowMuted == true) {
menu.findItem(R.id.shadow_mute_user)?.title = getString(if (member.value?.flags?.chatShadowMuted == true) {
R.string.unshadowmute_user
} else {
R.string.shadow_mute_user
})
menu.findItem(R.id.mute_user)?.title = getString(if (member?.flags?.chatRevoked == true) {
menu.findItem(R.id.mute_user)?.title = getString(if (member.value?.flags?.chatRevoked == true) {
R.string.unmute_user
} else {
R.string.mute_user
@ -247,7 +244,7 @@ class FullProfileActivity : BaseActivity() {
}
private fun muteUser() {
val isMuted = member?.flags?.chatRevoked == true
val isMuted = member.value?.flags?.chatRevoked == true
val alert = HabiticaAlertDialog(this)
if (isMuted) {
alert.setTitle(R.string.mute_user_confirm)
@ -256,7 +253,7 @@ class FullProfileActivity : BaseActivity() {
}
alert.addButton(R.string.yes, isPrimary = true, isDestructive = true) { _, _ ->
lifecycleScope.launchCatching {
member?.id?.let { socialRepository.updateMember(it, "flags.chatRevoked", !isMuted) }
member.value?.id?.let { socialRepository.updateMember(it, "flags.chatRevoked", !isMuted) }
refresh()
invalidateOptionsMenu()
}
@ -265,7 +262,7 @@ class FullProfileActivity : BaseActivity() {
}
private fun shadowMuteUser() {
val isBanned = member?.flags?.chatShadowMuted == true
val isBanned = member.value?.flags?.chatShadowMuted == true
val alert = HabiticaAlertDialog(this)
if (isBanned) {
alert.setTitle(R.string.shadowmute_user_confirm)
@ -274,7 +271,7 @@ class FullProfileActivity : BaseActivity() {
}
alert.addButton(R.string.yes, isPrimary = true, isDestructive = true) { _, _ ->
lifecycleScope.launchCatching {
member?.id?.let { socialRepository.updateMember(it, "flags.chatShadowMuted", !isBanned) }
member.value?.id?.let { socialRepository.updateMember(it, "flags.chatShadowMuted", !isBanned) }
refresh()
invalidateOptionsMenu()
}
@ -283,7 +280,7 @@ class FullProfileActivity : BaseActivity() {
}
private fun banUser() {
val isBanned = member?.authentication?.blocked == true
val isBanned = member.value?.authentication?.blocked == true
val alert = HabiticaAlertDialog(this)
if (isBanned) {
alert.setTitle(R.string.ban_user_confirm)
@ -292,7 +289,7 @@ class FullProfileActivity : BaseActivity() {
}
alert.addButton(R.string.yes, isPrimary = true, isDestructive = true) { _, _ ->
lifecycleScope.launchCatching {
member?.id?.let { socialRepository.updateMember(it, "auth.blocked", !isBanned) }
member.value?.id?.let { socialRepository.updateMember(it, "auth.blocked", !isBanned) }
refresh()
invalidateOptionsMenu()
}
@ -361,8 +358,6 @@ class FullProfileActivity : BaseActivity() {
}
binding.totalCheckinsView.text = user.loginIncentives.toString()
avatarWithBars?.updateData(user)
val status = mutableListOf<String>()
if (user.authentication?.blocked == true) status.add("Banned")
if (user.flags?.chatShadowMuted == true) status.add("Shadow Muted")

View file

@ -242,7 +242,10 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
binding.content.headerView.setContent {
HabiticaTheme {
AppHeaderView(viewModel.userViewModel) {
val user by viewModel.user.observeAsState(null)
val teamPlan by viewModel.userViewModel.currentTeamPlan.collectAsState(null)
val teamPlanMembers by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()
AppHeaderView(user, teamPlan, teamPlanMembers) {
showAsBottomSheet { onClose ->
val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState(null)
val members by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.activities
import android.app.Activity
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
@ -24,8 +25,10 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
@ -110,8 +113,11 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
val task by viewModel.task.observeAsState()
val titleModifier = Modifier.padding(top = 30.dp)
val textModifier = Modifier.padding(top = 4.dp)
val activity = LocalContext.current as? Activity
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 systemUiController = rememberSystemUiController()
val statusBarColor = HabiticaTheme.colors.primaryBackgroundFor(task)
val lightestColor = HabiticaTheme.colors.contentBackgroundFor(task)
@ -127,6 +133,10 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
) {
Button(
onClick = {
if (activity != null) {
activity.finish()
return@Button
}
MainNavigationController.navigateBack()
},
colors = ButtonDefaults.textButtonColors(contentColor = darkestColor),
@ -136,7 +146,7 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
painterResource(R.drawable.arrow_back),
stringResource(R.string.action_back),
colorFilter = ColorFilter.tint(
darkestColor
topTextColor
)
)
}
@ -144,7 +154,7 @@ fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
stringResource(R.string.task_summary),
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = darkestColor,
color = topTextColor,
modifier = Modifier.padding(start = 0.dp)
)
}

View file

@ -95,7 +95,7 @@ class ShopRecyclerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<an
val view = parent.inflate(R.layout.row_shopitem)
val viewHolder = ShopItemViewHolder(view)
viewHolder.shopIdentifier = shopIdentifier
viewHolder
viewHolder.onNeedsRefresh = onNeedsRefresh
viewHolder
}
}

View file

@ -113,7 +113,8 @@ fun AvatarOverviewView(userViewModel: MainUserViewModel,
verticalAlignment = Alignment.CenterVertically) {
Text(
stringResource(R.string.avatar_size),
style = HabiticaTheme.typography.subtitle2
style = HabiticaTheme.typography.subtitle2,
color = HabiticaTheme.colors.textSecondary
)
Spacer(modifier = Modifier.weight(1f))
SegmentedControl(items = listOf(stringResource(R.string.avatar_size_slim), stringResource(R.string.avatar_size_broad
@ -127,11 +128,13 @@ fun AvatarOverviewView(userViewModel: MainUserViewModel,
.padding(horizontal = 12.dp)
.padding(top = 15.dp),
verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.equipped), style = HabiticaTheme.typography.subtitle2)
Text(stringResource(R.string.equipped), style = HabiticaTheme.typography.subtitle2,
color = HabiticaTheme.colors.textSecondary)
Spacer(modifier = Modifier.weight(1f))
Text(
stringResource(R.string.equip_automatically),
style = HabiticaTheme.typography.body2
style = HabiticaTheme.typography.body2,
color = HabiticaTheme.colors.textPrimary
)
Switch(checked = user?.preferences?.autoEquip == true, onCheckedChange = {
userViewModel.updateUser("preferences.autoEquip", it)
@ -148,12 +151,14 @@ fun AvatarOverviewView(userViewModel: MainUserViewModel,
) {
Text(
stringResource(R.string.costume),
style = HabiticaTheme.typography.subtitle2
style = HabiticaTheme.typography.subtitle2,
color = HabiticaTheme.colors.textSecondary
)
Spacer(modifier = Modifier.weight(1f))
Text(
stringResource(R.string.wear_costume),
style = HabiticaTheme.typography.body2
style = HabiticaTheme.typography.body2,
color = HabiticaTheme.colors.textPrimary
)
Switch(checked = user?.preferences?.costume == true, onCheckedChange = {
userViewModel.updateUser("preferences.costume", it)

View file

@ -225,9 +225,9 @@ open class ShopFragment : BaseMainFragment<FragmentRefreshRecyclerviewBinding>()
}
Shop.SEASONAL_SHOP -> {
shop1.categories.sortWith(
compareBy<ShopCategory> { it.items.size != 1 }
.thenBy { it.items.firstOrNull()?.currency != "gold" }
compareBy<ShopCategory> { it.items.firstOrNull()?.currency != "gold" }
.thenByDescending { it.items.firstOrNull()?.event?.end }
.thenBy { it.items.firstOrNull()?.locked }
)
}
}

View file

@ -16,7 +16,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.google.android.material.composethemeadapter.createMdcTheme
import com.google.accompanist.themeadapter.material.createMdcTheme
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.common.habitica.extensions.getThemeColor
@ -27,7 +27,7 @@ fun HabiticaTheme(
) {
val context = LocalContext.current
val layoutDirection = LocalLayoutDirection.current
val (colors, _, shapes) = createMdcTheme(
val (colors, _, _) = createMdcTheme(
context = context,
layoutDirection = layoutDirection,
setTextColors = true

View file

@ -7,6 +7,7 @@ import android.graphics.drawable.BitmapDrawable
import android.text.method.LinkMovementMethod
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ChatItemBinding
@ -211,7 +212,15 @@ class ChatRecyclerMessageViewHolder(
}
if ((chatMessage?.flagCount ?: 0) > 0) {
binding.flagCountTextview.text = context.getString(R.string.message_flagged, (chatMessage?.flagCount ?: 0))
binding.flagCountTextview.isVisible = true
if (chatMessage?.flagCount == 1) {
binding.flagCountTextview.setTextColor(ContextCompat.getColor(context, R.color.text_orange))
} else {
binding.flagCountTextview.setTextColor(ContextCompat.getColor(context, R.color.text_red))
}
} else {
binding.flagCountTextview.isVisible = false
}
}

View file

@ -9,6 +9,7 @@ import com.habitrpg.android.habitica.databinding.PartyMemberBinding
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.getTranslatedClassName
class GroupMemberViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView), PopupMenu.OnMenuItemClickListener {
private val binding = PartyMemberBinding.bind(itemView)

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -63,7 +64,9 @@ abstract class ChecklistedViewHolder(
if (isLocked) {
this.checkmarkView.visibility = View.GONE
this.lockView.visibility = View.VISIBLE
lockView.drawable.setTint(ContextCompat.getColor(context, if (data.isDue == true || data.type == TaskType.TODO) data.extraExtraDarkTaskColor else R.color.text_dimmed))
val icon = AppCompatResources.getDrawable(context, R.drawable.task_lock)
icon?.setTint(ContextCompat.getColor(context, if (data.isDue == true || data.type == TaskType.TODO) data.extraExtraDarkTaskColor else R.color.text_dimmed))
lockView.setImageDrawable(icon)
} else {
this.checkmarkView.visibility = if (completed) View.VISIBLE else View.GONE
checkmarkView.drawable.setTint(ContextCompat.getColor(context, R.color.gray_400))

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.views
import android.content.res.Resources
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -26,9 +27,6 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -39,16 +37,27 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.bundleOf
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.models.TeamPlan
import com.habitrpg.android.habitica.models.auth.LocalAuthentication
import com.habitrpg.android.habitica.models.members.Member
import com.habitrpg.android.habitica.models.user.Authentication
import com.habitrpg.android.habitica.models.user.Profile
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.shared.habitica.models.Avatar
import com.habitrpg.shared.habitica.models.AvatarStats
import kotlin.random.Random
@Composable
fun UserLevelText(user: User) {
fun UserLevelText(user: Avatar) {
val text = if (user.hasClass) {
stringResource(
id = R.string.user_level_with_class,
@ -68,14 +77,23 @@ fun UserLevelText(user: User) {
)
}
fun AvatarStats.getTranslatedClassName(resources: Resources): String {
return when (habitClass) {
Stats.HEALER -> resources.getString(R.string.healer)
Stats.ROGUE -> resources.getString(R.string.rogue)
Stats.WARRIOR -> resources.getString(R.string.warrior)
Stats.MAGE -> resources.getString(R.string.mage)
else -> resources.getString(R.string.warrior)
}
}
@Composable
fun AppHeaderView(
viewModel: MainUserViewModel,
user: Avatar?,
teamPlan: TeamPlan? = null,
teamPlanMembers: List<Member>? = null,
onMemberRowClicked: () -> Unit
) {
val user by viewModel.user.observeAsState(null)
val teamPlan by viewModel.currentTeamPlan.collectAsState(null)
val teamPlanMembers by viewModel.currentTeamPlanMembers.observeAsState()
Column {
Row {
ComposableAvatarView(
@ -107,6 +125,7 @@ fun AppHeaderView(
maxValue = user?.stats?.toNextLevel?.toDouble() ?: 0.0,
displayCompact = teamPlan != null,
abbreviateValue = false,
abbreviateMax = false,
modifier = Modifier.weight(1f)
)
if (user?.hasClass == true) {
@ -114,10 +133,13 @@ fun AppHeaderView(
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,
value = user.stats?.mp ?: 0.0,
maxValue = user.stats?.maxMP?.toDouble() ?: 0.0,
displayCompact = teamPlan != null,
modifier = Modifier.weight(1f)
abbreviateValue = false,
abbreviateMax = false,
modifier = Modifier
.weight(1f)
.clickable {
MainNavigationController.navigate(R.id.skillsFragment)
}
@ -173,18 +195,18 @@ fun AppHeaderView(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(top = 12.dp, start = 12.dp, end = 12.dp)
.height(40.dp)
.width(72.dp)
.clip(MaterialTheme.shapes.medium)
.background(
colorResource(R.color.window_background)
)
.padding(top = 12.dp, start = 12.dp, end = 12.dp)
.clickable {
onMemberRowClicked()
}
) {
for (member in teamPlanMembers?.filter { it.id != user?.id }?.sortedBy { it.authentication?.timestamps?.lastLoggedIn }?.take(6) ?: emptyList()) {
for (member in teamPlanMembers?.filter { it.id != user?.id }?.sortedByDescending { it.authentication?.timestamps?.lastLoggedIn }?.take(6) ?: emptyList()) {
Box(modifier = Modifier
.clip(CircleShape)
.size(26.dp)
@ -205,21 +227,52 @@ fun AppHeaderView(
ClassIcon(className = user?.stats?.habitClass, hasClass = user?.hasClass ?: false, modifier = Modifier.padding(4.dp))
user?.let { UserLevelText(it) }
Spacer(Modifier.weight(1f))
if (user?.isSubscribed == true) {
user?.hourglassCount?.toDouble()
?.let {
CurrencyText(
"hourglasses",
it,
modifier = Modifier.padding(end = 12.dp).clickable {
MainNavigationController.navigate(R.id.subscriptionPurchaseActivity)
})
}
if (user is User && user.isSubscribed) {
CurrencyText(
"hourglasses",
user.hourglassCount.toDouble(),
modifier = Modifier
.padding(end = 12.dp)
.clickable {
MainNavigationController.navigate(R.id.subscriptionPurchaseActivity)
}, decimals = 0)
}
CurrencyText("gold", user?.stats?.gp ?: 0.0, modifier = Modifier.padding(end = 12.dp))
CurrencyText("gold", user?.stats?.gp ?: 0.0, modifier = Modifier.padding(end = 12.dp), decimals = 0)
CurrencyText("gems", user?.gemCount?.toDouble() ?: 0.0, modifier = Modifier.clickable {
MainNavigationController.navigate(R.id.gemPurchaseActivity)
})
}, decimals = 0)
}
}
}
private class UserProvider : PreviewParameterProvider<User> {
override val values: Sequence<User>
get() {
val list = mutableListOf<User>()
val member = User()
member.profile = Profile()
member.profile?.name = "User"
member.authentication = Authentication()
member.authentication?.localAuthentication = LocalAuthentication()
member.authentication?.localAuthentication?.username = "username"
member.stats = Stats()
member.stats?.hp = Random.nextDouble()
member.stats?.maxHealth = 50
member.stats?.toNextLevel = Random.nextInt()
member.stats?.exp =
Random.nextDouble(until = (member.stats?.toNextLevel ?: 0).toDouble())
member.stats?.maxMP = Random.nextInt()
member.stats?.mp = Random.nextDouble(until = (member.stats?.maxMP ?: 0).toDouble())
member.stats?.lvl = Random.nextInt()
list.add(member)
return list.asSequence()
}
}
@Composable
@Preview
private fun Preview(@PreviewParameter(UserProvider::class) user: User) {
AppHeaderView(user) {
}
}

View file

@ -77,6 +77,7 @@ private fun BottomSheetWrapper(
val radius = 20.dp
ModalBottomSheetLayout(
sheetBackgroundColor = Color.Transparent,
scrimColor = colorResource(R.color.content_background).copy(alpha = 0.3f),
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = radius, topEnd = radius),
sheetContent = {

View file

@ -0,0 +1,13 @@
package com.habitrpg.android.habitica.ui.views
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
@Composable
fun BuffIcon(buffed: Boolean?, modifier: Modifier = Modifier) {
if (buffed == true) {
Image(HabiticaIconsHelper.imageOfBuffIcon().asImageBitmap(), null, modifier = modifier)
}
}

View file

@ -23,12 +23,13 @@ fun CurrencyText(
value: Double,
modifier: Modifier = Modifier,
decimals: Int = 2,
minForAbbrevation: Int = 0
minForAbbrevation: Int = 0,
animated: Boolean = true
) {
val animatedValue = animateFloatAsState(
val animatedValue = if (animated) animateFloatAsState(
targetValue = value.toFloat(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
).value
).value else value.toFloat()
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
when (currency) {
"gold" -> HabiticaIconsHelper.imageOfGold()

View file

@ -1,6 +1,5 @@
package com.habitrpg.android.habitica.ui.views
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -18,7 +17,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
@ -47,43 +45,36 @@ fun GroupPlanMemberList(
) {
LazyColumn {
item {
Text(stringResource(R.string.member_list),
fontSize = 16.sp,
Text(
stringResource(R.string.member_list),
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = HabiticaTheme.colors.textTertiary,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 20.dp)
)
)
}
for (member in members?.sortedWith(compareByDescending<Member> {
group?.isLeader(
it.id ?: ""
)
}.thenByDescending {
group?.isManager(
it.id ?: ""
)
}.thenBy { it.username }) ?: emptyList()) {
item {
val role = if (group?.isLeader(member.id ?: "") == true) {
stringResource(R.string.owner)
} else if (group?.isManager(member.id ?: "") == true) {
stringResource(R.string.manager)
} else {
stringResource(R.string.member)
}
MemberItem(
member,
role,
onMemberClicked,
onMoreClicked,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
for (member in members?.sortedByDescending { it.authentication?.timestamps?.lastLoggedIn }
?: emptyList()) {
item {
val role = if (group?.isLeader(member.id ?: "") == true) {
stringResource(R.string.owner)
} else if (group?.isManager(member.id ?: "") == true) {
stringResource(R.string.manager)
} else {
stringResource(R.string.member)
}
MemberItem(
member,
role,
onMemberClicked,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
}
}
@Composable
@ -91,7 +82,6 @@ fun MemberItem(
member: Member,
role: String,
onMemberClicked: (String) -> Unit,
onMoreClicked: (Member) -> Unit,
modifier: Modifier = Modifier
) {
Box(
@ -107,10 +97,17 @@ fun MemberItem(
horizontalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier.padding(8.dp)
) {
ComposableAvatarView(avatar = member, modifier = Modifier
.padding(6.dp)
.size(94.dp, 98.dp))
Column(verticalArrangement = Arrangement.SpaceBetween, modifier = Modifier.height(104.dp).padding(end = 6.dp)) {
ComposableAvatarView(
avatar = member, modifier = Modifier
.padding(6.dp)
.size(94.dp, 98.dp)
)
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.height(104.dp)
.padding(end = 6.dp)
) {
Text(
member.displayName,
fontWeight = FontWeight.SemiBold,
@ -135,7 +132,12 @@ fun MemberItem(
modifier = Modifier.size(18.dp)
)
BuffIcon(member.stats?.isBuffed)
CurrencyText(currency = "gold", value = member.stats?.gp ?: 0.0)
CurrencyText(
currency = "gold",
value = (member.stats?.gp) ?: 0.0,
decimals = 0,
animated = false
)
}
LabeledBar(
color = colorResource(R.color.hpColor),
@ -143,7 +145,8 @@ fun MemberItem(
value = member.stats?.hp ?: 0.0,
maxValue = (member.stats?.maxHealth ?: 0).toDouble(),
displayCompact = true,
barHeight = 5.dp
barHeight = 5.dp,
animated = false
)
LabeledBar(
color = colorResource(R.color.xpColor),
@ -151,7 +154,8 @@ fun MemberItem(
value = member.stats?.exp ?: 0.0,
maxValue = (member.stats?.toNextLevel ?: 0).toDouble(),
displayCompact = true,
barHeight = 5.dp
barHeight = 5.dp,
animated = false
)
if (member.hasClass) {
LabeledBar(
@ -160,7 +164,8 @@ fun MemberItem(
value = member.stats?.mp ?: 0.0,
maxValue = (member.stats?.maxMP ?: 0).toDouble(),
displayCompact = true,
barHeight = 5.dp
barHeight = 5.dp,
animated = false
)
}
Row(horizontalArrangement = Arrangement.SpaceBetween) {
@ -181,13 +186,6 @@ fun MemberItem(
}
}
@Composable
fun BuffIcon(buffed: Boolean?, modifier: Modifier = Modifier) {
if (buffed == true) {
Image(HabiticaIconsHelper.imageOfBuffIcon().asImageBitmap(), null, modifier = modifier)
}
}
private class MemberProvider : PreviewParameterProvider<Member> {
override val values: Sequence<Member>
get() {
@ -217,5 +215,5 @@ private class MemberProvider : PreviewParameterProvider<Member> {
@Composable
@Preview
private fun Preview(@PreviewParameter(MemberProvider::class) member: Member) {
MemberItem(member = member, role = "Manager", onMemberClicked = {}, onMoreClicked = {})
MemberItem(member = member, role = "Manager", onMemberClicked = {})
}

View file

@ -2,7 +2,8 @@ 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.core.animateIntAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.Image
@ -16,7 +17,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.ProgressIndicatorDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -50,14 +50,15 @@ fun LabeledBar(
barHeight: Dp = 8.dp,
disabled: Boolean = false,
abbreviateValue: Boolean = true,
abbreviateMax: Boolean = true
abbreviateMax: Boolean = true,
animated: Boolean = true
) {
val cleanedMaxValue = java.lang.Double.max(1.0, maxValue)
val animatedValue = animateFloatAsState(
targetValue = value.toFloat(),
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
).value
val animatedValue = if (animated) animateIntAsState(
targetValue = value.toInt(),
animationSpec = spring()
).value else value.toInt()
val formatter = NumberFormat.getNumberInstance()
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 2
@ -92,7 +93,7 @@ fun LabeledBar(
if (!disabled) {
val currentValueText = if (abbreviateValue) NumberAbbreviator.abbreviate(
LocalContext.current,
animatedValue,
animatedValue.toFloat(),
0
) else formatter.format(animatedValue)
val maxValueText = if (abbreviateMax) NumberAbbreviator.abbreviate(

View file

@ -39,7 +39,7 @@ fun SegmentedControl(
onItemSelection: (selectedItemIndex: Int) -> Unit
) {
val selectedIndex = remember { mutableStateOf(defaultSelectedItemIndex) }
val color = MaterialTheme.colors.primary
val color = MaterialTheme.colors.surface
Row(
modifier = Modifier
) {

View file

@ -102,7 +102,7 @@ class SubscriptionDetailsView : LinearLayout {
val now = LocalDate.now()
val nextHourglassDate = LocalDate.now().plusMonths(plan.monthsUntilNextHourglass.toLong())
val format = if (now.year != nextHourglassDate.year) {
"MM YYYY"
"MMM YYYY"
} else {
"MMMM"
}

View file

@ -7,6 +7,7 @@ buildscript {
app_version_name = ''
app_version_code = 0
accompanist_version = '0.28.0'
amplitude_version = '1.5.1'
appcompat_version = '1.5.1'
coil_version = '2.2.2'

View file

@ -1,10 +1,9 @@
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
-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
New in 4.1:
-You can view, complete, assign, and add tasks to your Group Plans shared task board!
-Tap your name on a task screen to switch to different task boards
-Subscription details will show extra months and when youll get your next Hourglass
-More intuitive system notification settings
-Audio will be controlled by media volume now
-Task details are now tinted based on task health
-Fixed a bug that caused tasks to shuffle around
-Monthly Dailies should recur more consistently now

View file

@ -1,2 +1,2 @@
NAME=4.1
CODE=4841
CODE=4891

View file

@ -16,6 +16,7 @@ class User: Avatar {
override val currentPet: String?
get() = items?.currentPet
override var sleep: Boolean = false
override var id: String? = null
override var balance: Double = 0.0
@Json(ignore = true)
override var authentication: AvatarAuthentication? = null