mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-04-14 19:56:32 +00:00
Invitation improvements
This commit is contained in:
parent
5c46ffeaa0
commit
79d9d744b4
10 changed files with 110 additions and 63 deletions
|
|
@ -2,5 +2,4 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners android:topLeftRadius="22dp" android:topRightRadius="22dp" />
|
||||
<solid android:color="?background" />
|
||||
|
||||
</shape>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="?attr/colorContentBackground">
|
||||
<include layout="@layout/shop_header"
|
||||
android:id="@+id/npc_header"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:background="@color/window_background"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
android:background="@drawable/shop_header_background"
|
||||
android:clipChildren="true"
|
||||
android:clipToOutline="true"
|
||||
android:clipToPadding="true"
|
||||
android:layout_marginTop="8dp">
|
||||
<com.habitrpg.android.habitica.ui.views.NPCBannerView
|
||||
android:id="@+id/npcBannerView"
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@
|
|||
|
||||
<style name="SectionHeaderCaps">
|
||||
<item name="android:fontFamily">@string/font_family_medium</item>
|
||||
<item name="android:textSize">10sp</item>
|
||||
<item name="android:textSize">12sp</item>
|
||||
<item name="android:textColor">@color/text_ternary</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:layout_marginStart">@dimen/spacing_large</item>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ open class ChecklistItem : RealmObject, BaseMainObject, Parcelable {
|
|||
get() = "id"
|
||||
|
||||
@PrimaryKey
|
||||
var id: String? = null
|
||||
var id: String? = UUID.randomUUID().toString()
|
||||
var text: String? = null
|
||||
var completed: Boolean = false
|
||||
var position: Int = 0
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import java.time.ZonedDateTime
|
|||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.temporal.TemporalAccessor
|
||||
import java.util.UUID
|
||||
|
||||
open class RemindersItem : RealmObject, Parcelable {
|
||||
@PrimaryKey
|
||||
|
|
@ -43,7 +44,9 @@ open class RemindersItem : RealmObject, Parcelable {
|
|||
time = source.readString()
|
||||
}
|
||||
|
||||
constructor()
|
||||
constructor() {
|
||||
id = UUID.randomUUID().toString()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is RemindersItem) {
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@ import androidx.compose.animation.core.updateTransition
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -24,8 +23,8 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
|
|
@ -39,11 +38,9 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
|
@ -67,15 +64,23 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
fun uUIDFromStringOrNull(name: String): UUID? {
|
||||
return try {
|
||||
UUID.fromString(name)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@HiltViewModel
|
||||
class PartyInviteViewModel @Inject constructor(
|
||||
userRepository : UserRepository,
|
||||
userViewModel : MainUserViewModel,
|
||||
val socialRepository : SocialRepository
|
||||
userRepository: UserRepository,
|
||||
userViewModel: MainUserViewModel,
|
||||
val socialRepository: SocialRepository
|
||||
) : BaseViewModel(userRepository, userViewModel) {
|
||||
val invites = mutableStateListOf("")
|
||||
|
||||
suspend fun sendInvites() : List<InviteResponse>? {
|
||||
suspend fun sendInvites(): List<InviteResponse>? {
|
||||
val inviteMap = mapOf<String, MutableList<String>>(
|
||||
"emails" to mutableListOf(),
|
||||
"uuids" to mutableListOf(),
|
||||
|
|
@ -84,7 +89,7 @@ class PartyInviteViewModel @Inject constructor(
|
|||
for (invite in invites) {
|
||||
if (invite.isValidEmail()) {
|
||||
inviteMap["emails"]?.add(invite)
|
||||
} else if (UUID.fromString(invite) != null) {
|
||||
} else if (uUIDFromStringOrNull(invite) != null) {
|
||||
inviteMap["uuids"]?.add(invite)
|
||||
} else if (invite.isNotBlank()) {
|
||||
inviteMap["usernames"]?.add(invite)
|
||||
|
|
@ -96,22 +101,22 @@ class PartyInviteViewModel @Inject constructor(
|
|||
|
||||
@AndroidEntryPoint
|
||||
class PartyInviteFragment : BaseFragment<FragmentComposeBinding>() {
|
||||
val viewModel : PartyInviteViewModel by viewModels()
|
||||
val viewModel: PartyInviteViewModel by viewModels()
|
||||
|
||||
override var binding : FragmentComposeBinding? = null
|
||||
override var binding: FragmentComposeBinding? = null
|
||||
|
||||
override fun createBinding(
|
||||
inflater : LayoutInflater,
|
||||
container : ViewGroup?
|
||||
) : FragmentComposeBinding {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
): FragmentComposeBinding {
|
||||
return FragmentComposeBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater : LayoutInflater,
|
||||
container : ViewGroup?,
|
||||
savedInstanceState : Bundle?
|
||||
) : View? {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||
binding?.composeView?.setContent {
|
||||
HabiticaTheme {
|
||||
|
|
@ -125,18 +130,18 @@ class PartyInviteFragment : BaseFragment<FragmentComposeBinding>() {
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PartyInviteView(
|
||||
viewModel : PartyInviteViewModel
|
||||
viewModel: PartyInviteViewModel
|
||||
) {
|
||||
var inviteButtonState : LoadingButtonState by remember { mutableStateOf(LoadingButtonState.CONTENT) }
|
||||
var inviteButtonState: LoadingButtonState by remember { mutableStateOf(LoadingButtonState.CONTENT) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollableState = rememberScrollState()
|
||||
val invites = viewModel.invites
|
||||
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(14.dp)
|
||||
.scrollable(scrollableState, Orientation.Vertical)) {
|
||||
.scrollable(scrollableState, Orientation.Vertical)
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier
|
||||
|
|
@ -155,40 +160,61 @@ fun PartyInviteView(
|
|||
)
|
||||
}
|
||||
}
|
||||
items(invites.indices.toList()) { index ->
|
||||
val invite = invites[index]
|
||||
val transition = updateTransition(invites.size - 1 == index, label = "isLast")
|
||||
items(viewModel.invites.indices.toList()) { index ->
|
||||
val invite = viewModel.invites[index]
|
||||
val transition = updateTransition(viewModel.invites.size - 1 == index, label = "isLast")
|
||||
val rotation = transition.animateFloat(
|
||||
label = "isAssigned",
|
||||
transitionSpec = { spring(Spring.DampingRatioLowBouncy, Spring.StiffnessMediumLow) }
|
||||
) {
|
||||
if (it) 135f else 0f
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(0.dp, 4.dp)
|
||||
.background(HabiticaTheme.colors.windowBackground, HabiticaTheme.shapes.medium)
|
||||
.padding(4.dp, 4.dp)
|
||||
.animateItemPlacement()) {
|
||||
Image(
|
||||
painterResource(R.drawable.ic_close_white_24dp),
|
||||
null,
|
||||
colorFilter = ColorFilter.tint(HabiticaTheme.colors.textPrimary),
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(0.dp, 4.dp)
|
||||
.background(HabiticaTheme.colors.windowBackground, HabiticaTheme.shapes.medium)
|
||||
.padding(4.dp, 4.dp)
|
||||
.animateItemPlacement()
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
if (viewModel.invites.size - 1 >= index && viewModel.invites[index].isNotBlank()) {
|
||||
viewModel.invites.removeAt(index)
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors(),
|
||||
elevation = ButtonDefaults.elevation(0.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
modifier = Modifier
|
||||
.rotate(rotation.value)
|
||||
.size(32.dp)
|
||||
.padding(3.dp)
|
||||
)
|
||||
) {
|
||||
Image(
|
||||
painterResource(R.drawable.ic_close_white_24dp),
|
||||
null,
|
||||
colorFilter = ColorFilter.tint(HabiticaTheme.colors.textPrimary),
|
||||
modifier = Modifier
|
||||
.rotate(rotation.value)
|
||||
.size(32.dp)
|
||||
)
|
||||
}
|
||||
|
||||
TextField(
|
||||
value = invite, onValueChange = { value ->
|
||||
if (invites.size - 1 == index && invites[index].isBlank()) {
|
||||
if (viewModel.invites.size - 1 == index && viewModel.invites[index].isBlank()) {
|
||||
viewModel.invites.add("")
|
||||
}
|
||||
viewModel.invites[index] = value
|
||||
},
|
||||
singleLine = true,
|
||||
placeholder = { Text(stringResource(R.string.username_or_email)) },
|
||||
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent),
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
textColor = HabiticaTheme.colors.textPrimary
|
||||
),
|
||||
modifier = Modifier
|
||||
.onFocusChanged {
|
||||
if (!it.isFocused) {
|
||||
|
|
@ -202,7 +228,7 @@ fun PartyInviteView(
|
|||
}
|
||||
item {
|
||||
InviteButton(
|
||||
state = if (invites.any { it.isNotBlank() }) inviteButtonState else LoadingButtonState.DISABLED,
|
||||
state = if (viewModel.invites.any { it.isNotBlank() }) inviteButtonState else LoadingButtonState.DISABLED,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
inviteButtonState = LoadingButtonState.LOADING
|
||||
|
|
@ -213,6 +239,7 @@ fun PartyInviteView(
|
|||
if (responses?.isNotEmpty() == true) {
|
||||
inviteButtonState = LoadingButtonState.SUCCESS
|
||||
viewModel.invites.clear()
|
||||
viewModel.invites.add("")
|
||||
} else {
|
||||
inviteButtonState = LoadingButtonState.FAILED
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
|||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -73,8 +74,9 @@ class PartySeekingViewModel @Inject constructor(
|
|||
val socialRepository : SocialRepository
|
||||
) : BaseViewModel(userRepository, userViewModel) {
|
||||
val isRefreshing = mutableStateOf(false)
|
||||
var seekingUsers : Flow<PagingData<Member>>
|
||||
|
||||
val seekingUsers : Flow<PagingData<Member>>
|
||||
val successfulInvites = mutableStateListOf<String>()
|
||||
val inviteStates = mutableStateMapOf<String, LoadingButtonState>()
|
||||
init {
|
||||
seekingUsers = Pager(
|
||||
config = PagingConfig(
|
||||
|
|
@ -163,9 +165,6 @@ fun PartySeekingView(
|
|||
val pullRefreshState = rememberPullRefreshState(refreshing, { pageData.refresh() })
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val successfulInvites = remember { mutableListOf<String>() }
|
||||
val inviteStates = remember { mutableMapOf<String, LoadingButtonState>() }
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -209,26 +208,26 @@ fun PartySeekingView(
|
|||
if (it == null) return@items
|
||||
PartySeekingListItem(
|
||||
user = it,
|
||||
inviteState = inviteStates[it.id] ?: LoadingButtonState.CONTENT,
|
||||
isInvited = successfulInvites.contains(it.id),
|
||||
inviteState =viewModel.inviteStates[it.id] ?: LoadingButtonState.CONTENT,
|
||||
isInvited = viewModel.successfulInvites.contains(it.id),
|
||||
modifier = Modifier.animateItemPlacement()
|
||||
) { member ->
|
||||
scope.launchCatching({
|
||||
inviteStates[member.id] = LoadingButtonState.FAILED
|
||||
viewModel.inviteStates[member.id] = LoadingButtonState.FAILED
|
||||
}) {
|
||||
inviteStates[member.id] = LoadingButtonState.LOADING
|
||||
val response = if (successfulInvites.contains(member.id)) viewModel.inviteUser(member) else viewModel.inviteUser(member)
|
||||
viewModel.inviteStates[member.id] = LoadingButtonState.LOADING
|
||||
val response: Any? = if (viewModel.successfulInvites.contains(member.id)) viewModel.rescindInvite(member) else viewModel.inviteUser(member)
|
||||
if (response != null) {
|
||||
if (successfulInvites.contains(member.id)) {
|
||||
successfulInvites.remove(member.id)
|
||||
if (viewModel.successfulInvites.contains(member.id)) {
|
||||
viewModel.successfulInvites.remove(member.id)
|
||||
} else {
|
||||
successfulInvites.add(member.id)
|
||||
viewModel.successfulInvites.add(member.id)
|
||||
}
|
||||
inviteStates[member.id] = LoadingButtonState.SUCCESS
|
||||
viewModel.inviteStates[member.id] = LoadingButtonState.SUCCESS
|
||||
delay(4.toDuration(DurationUnit.SECONDS))
|
||||
inviteStates[member.id] = LoadingButtonState.CONTENT
|
||||
viewModel.inviteStates[member.id] = LoadingButtonState.CONTENT
|
||||
} else {
|
||||
inviteStates[member.id] = LoadingButtonState.FAILED
|
||||
viewModel.inviteStates[member.id] = LoadingButtonState.FAILED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,19 +7,21 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.habitrpg.android.habitica.R
|
||||
import com.habitrpg.android.habitica.data.FAQRepository
|
||||
import com.habitrpg.android.habitica.databinding.FragmentFaqOverviewBinding
|
||||
import com.habitrpg.android.habitica.databinding.SupportFaqItemBinding
|
||||
import com.habitrpg.android.habitica.helpers.AppConfigManager
|
||||
import com.habitrpg.android.habitica.helpers.MainNavigationController
|
||||
import com.habitrpg.android.habitica.ui.fragments.BaseMainFragment
|
||||
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
|
||||
import com.habitrpg.common.habitica.extensions.layoutInflater
|
||||
import com.habitrpg.common.habitica.helpers.launchCatching
|
||||
import com.habitrpg.common.habitica.helpers.setMarkdown
|
||||
import javax.inject.Inject
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FAQOverviewFragment : BaseMainFragment<FragmentFaqOverviewBinding>() {
|
||||
|
|
@ -32,6 +34,8 @@ class FAQOverviewFragment : BaseMainFragment<FragmentFaqOverviewBinding>() {
|
|||
|
||||
@Inject
|
||||
lateinit var faqRepository: FAQRepository
|
||||
@Inject
|
||||
lateinit var configManager: AppConfigManager
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
|
@ -46,6 +50,11 @@ class FAQOverviewFragment : BaseMainFragment<FragmentFaqOverviewBinding>() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding?.npcHeader?.npcBannerView?.shopSpriteSuffix = configManager.shopSpriteSuffix()
|
||||
binding?.npcHeader?.npcBannerView?.identifier = "tavern"
|
||||
binding?.npcHeader?.namePlate?.setText(R.string.tavern_owner)
|
||||
binding?.npcHeader?.descriptionView?.isVisible = false
|
||||
|
||||
binding?.healthSection?.findViewById<ImageView>(R.id.icon_view)?.setImageBitmap(
|
||||
HabiticaIconsHelper.imageOfHeartLarge()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0'
|
||||
}
|
||||
|
||||
include 'Habitica', ':Habitica'
|
||||
include ':wearos'
|
||||
include ':common'
|
||||
|
|
|
|||
Loading…
Reference in a new issue