mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 19:56:32 +00:00
Fix header
This commit is contained in:
parent
06c94a4f21
commit
118581db52
20 changed files with 338 additions and 143 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class PartyInvitePagerFragment : BaseMainFragment<FragmentViewpagerBinding>() {
|
|||
) : View? {
|
||||
this.usesTabLayout = true
|
||||
this.hidesToolbar = true
|
||||
showsBackButton = true
|
||||
return super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
NAME=4.1.8
|
||||
CODE=5681
|
||||
CODE=5701
|
||||
Loading…
Reference in a new issue