Fix header

This commit is contained in:
Phillip Thelen 2023-03-22 13:25:19 +01:00
parent 06c94a4f21
commit 118581db52
20 changed files with 338 additions and 143 deletions

View file

@ -118,6 +118,7 @@ dependencies {
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.material:material:1.3.1"
implementation "androidx.compose.animation:animation:$compose_version"
implementation "androidx.compose.ui:ui-text-google-fonts:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"

View file

@ -464,6 +464,6 @@ interface ApiService {
@POST("tasks/{taskID}/needs-work/{userID}")
suspend fun markTaskNeedsWork(@Path("taskID") taskID: String, @Path("userID") userID: String): HabitResponse<Task>
@GET("party-seekers")
@GET("looking-for-party")
suspend fun retrievePartySeekingUsers(): HabitResponse<List<Member>>
}

View file

@ -139,9 +139,9 @@ class InventoryRepositoryImpl(
return sellItem(item)
}
override suspend fun sellItem(ownedItem: OwnedItem): User? {
val item = localRepository.getItem(ownedItem.itemType ?: "", ownedItem.key ?: "").firstOrNull() ?: return null
return sellItem(item, ownedItem)
override suspend fun sellItem(item: OwnedItem): User? {
val itemData = localRepository.getItem(item.itemType ?: "", item.key ?: "").firstOrNull() ?: return null
return sellItem(itemData, item)
}
override fun getLatestMysteryItem(): Flow<Equipment> {

View file

@ -0,0 +1,30 @@
package com.habitrpg.android.habitica.helpers
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.flowWithLifecycle
import kotlinx.coroutines.flow.Flow
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
@Composable
fun <T> rememberFlow(
flow: Flow<T>,
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current
): Flow<T> {
return remember(key1 = flow, key2 = lifecycleOwner) { flow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) }
}
@Composable
fun <T : R, R> Flow<T>.collectAsStateLifecycleAware(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R> {
val lifecycleAwareFlow = rememberFlow(flow = this)
return lifecycleAwareFlow.collectAsState(initial = initial, context = context)
}

View file

@ -100,8 +100,11 @@ open class User : RealmObject(), BaseMainObject, Avatar, VersionedObject {
val contributorColor: Int
get() = this.contributor?.contributorColor ?: R.color.text_primary
override val hourglassCount: Int
override var hourglassCount: Int
get() = purchased?.plan?.consecutive?.trinkets ?: 0
set(value) {
purchased?.plan?.consecutive?.trinkets = value
}
val hasParty: Boolean
get() {

View file

@ -14,6 +14,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Named
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
@ -38,6 +39,7 @@ class UserModule {
}
@Provides
@Singleton
fun providesUserViewModel(
@Named(NAMED_USER_ID) userID: String,
userRepository: UserRepository,

View file

@ -6,7 +6,6 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -23,7 +22,6 @@ import androidx.compose.material.ButtonDefaults
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.DrawerState
import androidx.compose.material.DrawerValue
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.Scaffold
import androidx.compose.material.ScaffoldState
import androidx.compose.material.SnackbarHostState
@ -44,7 +42,6 @@ import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
@ -60,7 +57,6 @@ import coil.compose.AsyncImage
import com.android.billingclient.api.ProductDetails
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.extensions.addCloseButton
import com.habitrpg.android.habitica.helpers.AppConfigManager
@ -69,6 +65,7 @@ import com.habitrpg.android.habitica.helpers.PurchaseHandler
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.android.habitica.ui.viewmodels.MainUserViewModel
import com.habitrpg.android.habitica.ui.views.CurrencyText
import com.habitrpg.android.habitica.ui.views.HabiticaButton
import com.habitrpg.android.habitica.ui.views.PixelArtView
import com.habitrpg.android.habitica.ui.views.dialogs.HabiticaAlertDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
@ -613,34 +610,6 @@ fun FourFreeItem(
}
}
@Composable
fun HabiticaButton(
background: Color,
color: Color,
onClick: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.background(background, HabiticaTheme.shapes.medium)
.clickable { onClick() }
.fillMaxWidth()
.padding(8.dp)
) {
ProvideTextStyle(
value = TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.SemiBold,
color = color
)
) {
content()
}
}
}
@Preview(device = Devices.PIXEL_4)
@Preview(device = Devices.PIXEL_4, uiMode = UI_MODE_NIGHT_YES)
@Composable

View file

@ -9,6 +9,7 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
@ -42,6 +43,7 @@ import com.habitrpg.android.habitica.helpers.AppConfigManager
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.NotificationOpenHandler
import com.habitrpg.android.habitica.helpers.SoundManager
import com.habitrpg.android.habitica.helpers.collectAsStateLifecycleAware
import com.habitrpg.android.habitica.interactors.CheckClassSelectionUseCase
import com.habitrpg.android.habitica.interactors.DisplayItemDropUseCase
import com.habitrpg.android.habitica.interactors.NotifyUserUseCase
@ -255,12 +257,18 @@ open class MainActivity : BaseActivity(), SnackbarActivity {
setupNotifications()
setupBottomnavigationLayoutListener()
lifecycleScope.launch {
viewModel.userViewModel.currentTeamPlan.collect {
Log.d("asdf", it?.toString() ?: "")
}
}
binding.content.headerView.setContent {
HabiticaTheme {
val user by viewModel.user.observeAsState(null)
val teamPlan by viewModel.userViewModel.currentTeamPlan.collectAsState(null)
val teamPlan by viewModel.userViewModel.currentTeamPlan.collectAsStateLifecycleAware(null)
val teamPlanMembers by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()
AppHeaderView(user, teamPlan, teamPlanMembers) {
AppHeaderView(user, teamPlan = teamPlan, teamPlanMembers = teamPlanMembers) {
showAsBottomSheet { onClose ->
val group by viewModel.userViewModel.currentTeamPlanGroup.collectAsState(null)
val members by viewModel.userViewModel.currentTeamPlanMembers.observeAsState()

View file

@ -57,11 +57,14 @@ import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.UserRow
import com.habitrpg.shared.habitica.models.tasks.TaskType
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import java.text.DateFormat
import java.util.Date
import javax.inject.Inject
class TaskSummaryViewModel(
@HiltViewModel
class TaskSummaryViewModel @Inject constructor(
userRepository : UserRepository,
userViewModel : MainUserViewModel,
val taskRepository : TaskRepository,

View file

@ -41,7 +41,7 @@ import com.habitrpg.android.habitica.helpers.notifications.PushNotificationManag
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.prefs.TimePreference
import com.habitrpg.android.habitica.ui.activities.ClassSelectionActivity
import com.habitrpg.android.habitica.ui.activities.HabiticaButton
import com.habitrpg.android.habitica.ui.views.HabiticaButton
import com.habitrpg.android.habitica.ui.activities.MainActivity
import com.habitrpg.android.habitica.ui.activities.PrefsActivity
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme

View file

@ -31,6 +31,7 @@ class PartyInvitePagerFragment : BaseMainFragment<FragmentViewpagerBinding>() {
) : View? {
this.usesTabLayout = true
this.hidesToolbar = true
showsBackButton = true
return super.onCreateView(inflater, container, savedInstanceState)
}

View file

@ -425,7 +425,8 @@ class TasksFragment : BaseMainFragment<FragmentViewpagerBinding>(), SearchView.O
mainActivity?.title = viewModel.ownerTitle
MainNavigationController.updateLabel(R.id.tasksFragment, viewModel.ownerTitle.toString())
}
viewModel.userViewModel.currentTeamPlan.value = viewModel.teamPlans[viewModel.ownerID.value]
val teamPlan = viewModel.teamPlans[viewModel.ownerID.value]
viewModel.userViewModel.currentTeamPlan.tryEmit(teamPlan)
lifecycleScope.launchCatching {
bottomNavigation?.canAddTasks = viewModel.canAddTasks()
}

View file

@ -118,6 +118,7 @@ val Typography.subtitle3
fontSize = 16.sp,
letterSpacing = 0.15.sp
)
object HabiticaTheme {
val typography: Typography
@Composable
@ -205,3 +206,7 @@ class HabiticaColors(
}
}
}
class HabiticaTypography {
}

View file

@ -1,5 +1,6 @@
package com.habitrpg.android.habitica.ui.viewmodels
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import com.habitrpg.android.habitica.data.SocialRepository
@ -10,7 +11,8 @@ import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.common.habitica.helpers.ExceptionHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
@ -37,11 +39,16 @@ class MainUserViewModel @Inject constructor(private val providedUserID: String,
get() = user.value?.preferences?.tasks?.mirrorGroupTasks ?: emptyList()
val user: LiveData<User?> = userRepository.getUser().asLiveData()
var currentTeamPlan: MutableStateFlow<TeamPlan?> = MutableStateFlow(null)
var currentTeamPlan = MutableSharedFlow<TeamPlan?>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST,
)
@OptIn(ExperimentalCoroutinesApi::class)
var currentTeamPlanGroup = currentTeamPlan
.filterNotNull()
.distinctUntilChanged { old, new -> old.id == new.id }
.distinctUntilChanged { old, new ->
Log.d("asfd", "${old.id} - ${new.id}")
old.id == new.id }
.flatMapLatest { socialRepository.getGroup(it.id) }
@OptIn(ExperimentalCoroutinesApi::class)
var currentTeamPlanMembers: LiveData<List<Member>> = currentTeamPlan

View file

@ -1,7 +1,12 @@
package com.habitrpg.android.habitica.ui.views
import android.content.res.Resources
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
@ -14,6 +19,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@ -26,6 +32,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -49,14 +56,19 @@ 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.Flags
import com.habitrpg.android.habitica.models.user.Preferences
import com.habitrpg.android.habitica.models.user.Profile
import com.habitrpg.android.habitica.models.user.Purchases
import com.habitrpg.android.habitica.models.user.Stats
import com.habitrpg.android.habitica.models.user.SubscriptionPlan
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
import com.habitrpg.shared.habitica.models.Avatar
import kotlin.random.Random
@Composable
fun UserLevelText(user: Avatar) {
fun UserLevelText(user : Avatar) {
val text = if (user.hasClass) {
stringResource(
id = R.string.user_level_with_class,
@ -77,7 +89,7 @@ fun UserLevelText(user: Avatar) {
)
}
fun getTranslatedClassName(resources: Resources, className: String?): String {
fun getTranslatedClassName(resources : Resources, className : String?) : String {
return when (className) {
Stats.HEALER -> resources.getString(R.string.healer)
Stats.ROGUE -> resources.getString(R.string.rogue)
@ -87,14 +99,16 @@ fun getTranslatedClassName(resources: Resources, className: String?): String {
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun AppHeaderView(
user: Avatar?,
teamPlan: TeamPlan? = null,
teamPlanMembers: List<Member>? = null,
onMemberRowClicked: () -> Unit
user : Avatar?,
modifier : Modifier = Modifier,
teamPlan : TeamPlan? = null,
teamPlanMembers : List<Member>? = null,
onMemberRowClicked : () -> Unit
) {
Column {
Column(modifier) {
Row {
ComposableAvatarView(
user,
@ -105,9 +119,15 @@ fun AppHeaderView(
MainNavigationController.navigate(R.id.avatarOverviewFragment)
}
)
val animationValue = animateFloatAsState(targetValue = if (teamPlan != null) 1f else 0f).value
val animationValue =
animateFloatAsState(targetValue = if (teamPlan != null) 1f else 0f).value
Box(modifier = Modifier.height(100.dp)) {
Column(Modifier.padding(bottom = (animationValue * 48f).dp, end = (animationValue * 80f).dp)) {
Column(
Modifier.padding(
bottom = (animationValue * 48f).dp,
end = (animationValue * 80f).dp
)
) {
LabeledBar(
icon = HabiticaIconsHelper.imageOfHeartLightBg(),
label = stringResource(R.string.HP_default),
@ -155,6 +175,21 @@ fun AppHeaderView(
disabled = true,
modifier = Modifier.weight(1f)
)
} else if (user?.preferences?.disableClasses != true && user?.flags?.classSelected == false) {
HabiticaButton(
background = MaterialTheme.colors.primarySurface,
color = MaterialTheme.colors.onPrimary,
onClick = {
MainNavigationController.navigate(R.id.classSelectionActivity)
},
contentPadding = PaddingValues(0.dp),
fontSize = 14.sp,
modifier = Modifier.height(28.dp)
) {
Text(stringResource(R.string.choose_class))
}
} else {
Spacer(modifier = Modifier.weight(1f))
}
}
val animWidth = with(LocalDensity.current) { 48.dp.roundToPx() }
@ -197,47 +232,67 @@ fun AppHeaderView(
exit = slideOutVertically { animHeight } + fadeOut(),
modifier = Modifier.align(Alignment.BottomCenter)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.width(72.dp)
.clip(MaterialTheme.shapes.medium)
.background(
colorResource(R.color.window_background)
AnimatedContent(targetState = teamPlanMembers?.filter { it.id != user?.id },
transitionSpec = {
ContentTransform(
targetContentEnter = fadeIn(animationSpec = tween(200, easing = FastOutSlowInEasing)) + slideInVertically { height -> height },
initialContentExit = fadeOut(animationSpec = tween(200)) + slideOutVertically { height -> -height }
)
.padding(start = 12.dp, end = 12.dp)
.clickable {
onMemberRowClicked()
}
) {
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)
.padding(end = 6.dp, top = 4.dp)
) {
ComposableAvatarView(
avatar = member,
Modifier
.size(64.dp)
.requiredSize(64.dp)
}) {members ->
Row(
horizontalArrangement = Arrangement.spacedBy(
12.dp,
Alignment.CenterHorizontally
),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.width(72.dp)
.clip(MaterialTheme.shapes.medium)
.background(
colorResource(R.color.window_background)
)
.padding(start = 12.dp, end = 12.dp)
.clickable {
onMemberRowClicked()
}
) {
for (member in members
?.sortedByDescending { it.authentication?.timestamps?.lastLoggedIn }
?.take(6) ?: emptyList()) {
Box(
modifier = Modifier
.clip(CircleShape)
.size(26.dp)
.padding(end = 6.dp, top = 4.dp)
) {
ComposableAvatarView(
avatar = member,
Modifier
.size(64.dp)
.requiredSize(64.dp)
)
}
}
}
}
}
}
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.defaultMinSize(minHeight = 28.dp)) {
ClassIcon(className = user?.stats?.habitClass, hasClass = user?.hasClass ?: false, modifier = Modifier.padding(4.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.defaultMinSize(minHeight = 28.dp)
) {
ClassIcon(
className = user?.stats?.habitClass,
hasClass = user?.hasClass ?: false,
modifier = Modifier.padding(4.dp)
)
user?.let { UserLevelText(it) }
Spacer(Modifier.weight(1f))
if (user is User) {
if (user.isSubscribed) {
if (user.isSubscribed || user.hourglassCount > 0) {
CurrencyText(
"hourglasses",
user.hourglassCount.toDouble(),
@ -269,33 +324,77 @@ fun AppHeaderView(
}
}
private class UserProvider : PreviewParameterProvider<User> {
override val values: Sequence<User>
private class UserProvider : PreviewParameterProvider<Pair<User, TeamPlan?>> {
private fun generateMember() : User {
val member = User()
member.profile = Profile()
member.profile?.name = "User"
member.authentication = Authentication()
member.authentication?.localAuthentication = LocalAuthentication()
member.authentication?.localAuthentication?.username = "username"
member.preferences = Preferences()
member.preferences?.disableClasses = false
member.flags = Flags()
member.flags?.classSelected = true
member.purchased = Purchases()
member.purchased?.plan = SubscriptionPlan()
member.stats = Stats()
member.stats?.hp = Random.nextDouble(from = 0.0, until = 50.0)
member.stats?.maxHealth = 50
member.stats?.toNextLevel = Random.nextInt(from = 0, until = 10000)
member.stats?.exp =
Random.nextDouble(until = (member.stats?.toNextLevel ?: 0).toDouble())
member.stats?.maxMP = Random.nextInt(from = 0, until = 10000)
member.stats?.mp = Random.nextDouble(until = (member.stats?.maxMP ?: 0).toDouble())
member.stats?.lvl = Random.nextInt(from = 0, until = 9999)
return member
}
override val values : Sequence<Pair<User, TeamPlan?>>
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)
val list = mutableListOf<Pair<User, TeamPlan?>>()
val earlyMember = generateMember()
earlyMember.stats?.lvl = 5
list.add(Pair(earlyMember, null))
val needsClass = generateMember()
needsClass.stats?.lvl = 24
needsClass.stats?.habitClass = "healer"
needsClass.flags?.classSelected = false
list.add(Pair(needsClass, null))
val classDisabled = generateMember()
classDisabled.stats?.lvl = 24
classDisabled.stats?.habitClass = "rogue"
classDisabled.preferences?.disableClasses = true
list.add(Pair(classDisabled, null))
val subscriber = generateMember()
subscriber.purchased?.plan?.planId = "basic_earned"
subscriber.purchased?.plan?.customerId = "123"
subscriber.stats?.habitClass = "warrior"
list.add(Pair(subscriber, null))
val onlyHourglasses = generateMember()
onlyHourglasses.hourglassCount = 3
onlyHourglasses.stats?.habitClass = "wizard"
list.add(Pair(onlyHourglasses, null))
val teamplanUser = generateMember()
val teamPlan = TeamPlan()
list.add(Pair(teamplanUser, teamPlan))
return list.asSequence()
}
}
@Composable
@Preview
private fun Preview(@PreviewParameter(UserProvider::class) user: User) {
AppHeaderView(user) {
private fun Preview(@PreviewParameter(UserProvider::class) data: Pair<User, TeamPlan>) {
HabiticaTheme {
AppHeaderView(
data.first,
teamPlan = data.second,
modifier = Modifier
.background(HabiticaTheme.colors.contentBackground)
.padding(8.dp)
) {
}
}
}

View file

@ -0,0 +1,49 @@
package com.habitrpg.android.habitica.ui.views
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ProvideTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.habitrpg.android.habitica.ui.theme.HabiticaTheme
@Composable
fun HabiticaButton(
background : Color,
color : Color,
onClick : () -> Unit,
modifier : Modifier = Modifier,
contentPadding : PaddingValues = PaddingValues(8.dp),
fontSize : TextUnit = 18.sp,
content : @Composable () -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.background(background, HabiticaTheme.shapes.medium)
.clickable { onClick() }
.fillMaxWidth()
.padding(contentPadding)
) {
ProvideTextStyle(
value = TextStyle(
fontSize = fontSize,
fontWeight = FontWeight.SemiBold,
color = color
)
) {
content()
}
}
}

View file

@ -2,12 +2,17 @@ package com.habitrpg.android.habitica.ui.views
import android.graphics.Bitmap
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -19,6 +24,10 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@ -39,19 +48,19 @@ import java.text.NumberFormat
@Composable
fun LabeledBar(
modifier: Modifier = Modifier,
icon: Bitmap? = null,
label: String? = null,
color: Color = colorResource(R.color.brand),
barColor: Color = HabiticaTheme.colors.windowBackground,
value: Double,
maxValue: Double,
displayCompact: Boolean = false,
barHeight: Dp = 8.dp,
disabled: Boolean = false,
abbreviateValue: Boolean = true,
abbreviateMax: Boolean = true,
animated: Boolean = true
modifier : Modifier = Modifier,
icon : Bitmap? = null,
label : String? = null,
color : Color = colorResource(R.color.brand),
barColor : Color = HabiticaTheme.colors.windowBackground,
value : Double,
maxValue : Double,
displayCompact : Boolean = false,
barHeight : Dp = 8.dp,
disabled : Boolean = false,
abbreviateValue : Boolean = true,
abbreviateMax : Boolean = true,
animated : Boolean = true
) {
val cleanedMaxValue = java.lang.Double.max(1.0, maxValue)
@ -62,22 +71,31 @@ fun LabeledBar(
val formatter = NumberFormat.getNumberInstance()
formatter.minimumFractionDigits = 0
formatter.maximumFractionDigits = 2
Row(
verticalAlignment = Alignment.CenterVertically,
val animatedPadding = animateDpAsState(
targetValue = if (displayCompact) {
0.dp
} else {
24.dp
}
)
Box(
modifier = modifier.alpha(if (disabled) 0.5f else 1.0f)
) {
icon?.let {
AnimatedVisibility(
visible = !displayCompact,
enter = slideInHorizontally { -18 },
exit = slideOutHorizontally { -18 }
enter = fadeIn() + slideInHorizontally { -18 },
exit = fadeOut() + slideOutHorizontally { -18 },
modifier = Modifier.align(Alignment.CenterStart)
) {
Image(
it.asImageBitmap(), null, modifier = Modifier.padding(end = 8.dp)
it.asImageBitmap(), null, modifier = Modifier
)
}
}
Column(modifier = Modifier.weight(1f)) {
Column(modifier = Modifier.padding(start = animatedPadding.value)) {
LinearProgressIndicator(
progress = (animatedValue / cleanedMaxValue).toFloat(),
Modifier
@ -122,14 +140,22 @@ fun LabeledBar(
@Composable
@Preview
private fun Preview() {
Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.width(180.dp)) {
var compact : Boolean by remember { mutableStateOf(false) }
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.width(240.dp)
.padding(8.dp)
.clickable {
compact = !compact
}) {
LabeledBar(
icon = HabiticaIconsHelper.imageOfHeartLightBg(),
label = stringResource(id = R.string.health),
color = colorResource(R.color.hpColor),
value = 10.0,
maxValue = 50.0,
displayCompact = false
displayCompact = compact
)
LabeledBar(
icon = HabiticaIconsHelper.imageOfExperience(),
@ -137,7 +163,7 @@ private fun Preview() {
color = colorResource(R.color.xpColor),
value = 100123.0,
maxValue = 50000000000000.0,
displayCompact = false,
displayCompact = compact,
abbreviateValue = false
)
LabeledBar(
@ -145,8 +171,8 @@ private fun Preview() {
label = stringResource(id = R.string.XP_default),
color = colorResource(R.color.xpColor),
value = 100123.0,
maxValue = 50000000000000.0,
displayCompact = false,
maxValue = 500000000000.0,
displayCompact = compact,
abbreviateValue = false,
abbreviateMax = false
)
@ -156,8 +182,8 @@ private fun Preview() {
color = colorResource(R.color.mpColor),
value = 10.0,
maxValue = 5000.0,
displayCompact = false,
disabled = true
displayCompact = compact,
disabled = false
)
}
}

View file

@ -28,11 +28,12 @@ class MemberSerialization : JsonDeserializer<Member> {
val realm = Realm.getDefaultInstance()
var member = realm.where(Member::class.java).equalTo("id", id).findFirst() ?: Member()
if (member.id == null) {
if (member.id.isBlank()) {
member.id = id
} else {
member = realm.copyFromRealm(member)
}
realm.close()
if (obj.has("flags")) {
member.flags = context.deserialize(obj.get("flags"), MemberFlags::class.java)
@ -99,8 +100,6 @@ class MemberSerialization : JsonDeserializer<Member> {
}
member.id = member.id
realm.close()
return member
}
}

View file

@ -5,12 +5,4 @@ import com.habitrpg.android.habitica.components.DaggerAppComponent;
import com.habitrpg.android.habitica.modules.AppModule;
public class HabiticaApplication extends HabiticaBaseApplication {
@Override
protected AppComponent initDagger() {
return DaggerAppComponent.builder()
.appModule(new AppModule(this))
.developerModule(new ReleaseDeveloperModule())
.build();
}
}

View file

@ -1,2 +1,2 @@
NAME=4.1.8
CODE=5681
CODE=5701