feat: loading masking and further error handling for profile settings

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-12-09 20:32:13 +01:00
parent 331d7d2b68
commit d4f994ebf4
1 changed files with 145 additions and 106 deletions

View File

@ -13,11 +13,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -29,6 +31,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -56,12 +59,14 @@ import javax.inject.Inject
@Suppress("StaticFieldLeak") @Suppress("StaticFieldLeak")
class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val context: Context) : class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val context: Context) :
ViewModel() { ViewModel() {
var isLoading by mutableStateOf(true)
var pfpModel by mutableStateOf<Any?>(null) var pfpModel by mutableStateOf<Any?>(null)
var currentProfile by mutableStateOf<Profile?>(null) var currentProfile by mutableStateOf<Profile?>(null)
var pendingProfile by mutableStateOf<Profile?>(null) var pendingProfile by mutableStateOf<Profile?>(null)
var backgroundModel by mutableStateOf<Any?>(null) var backgroundModel by mutableStateOf<Any?>(null)
var uploadProgress by mutableFloatStateOf(0f) var uploadProgress by mutableFloatStateOf(0f)
var uploadError by mutableStateOf<String?>(null) var uploadError by mutableStateOf<String?>(null)
var bioError by mutableStateOf<String?>(null)
init { init {
RevoltAPI.selfId?.let { self -> RevoltAPI.selfId?.let { self ->
@ -75,6 +80,8 @@ class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val
} }
pendingProfile = currentProfile!!.copy() pendingProfile = currentProfile!!.copy()
isLoading = false
} }
} }
@ -203,12 +210,17 @@ class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val
} }
fun saveBio() { fun saveBio() {
bioError = null
viewModelScope.launch { viewModelScope.launch {
patchSelf(bio = pendingProfile?.content) try {
patchSelf(bio = pendingProfile?.content)
fetchUserProfile(RevoltAPI.selfId!!).let { fetchUserProfile(RevoltAPI.selfId!!).let {
currentProfile = it currentProfile = it
pendingProfile = it pendingProfile = it
}
} catch (e: Exception) {
bioError = e.message
} }
} }
} }
@ -238,126 +250,153 @@ fun ProfileSettingsScreen(
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
RevoltAPI.userCache[RevoltAPI.selfId]?.let { if (viewModel.isLoading) {
RawUserOverview(
it,
viewModel.pendingProfile,
viewModel.pfpModel?.toString(),
viewModel.backgroundModel?.toString()
)
}
AnimatedVisibility(visible = viewModel.uploadProgress > 0f) {
LinearProgressIndicator(
progress = viewModel.uploadProgress,
modifier = Modifier
.fillMaxSize()
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
)
}
AnimatedVisibility(visible = viewModel.uploadError != null) {
Text(
text = viewModel.uploadError ?: "",
style = MaterialTheme.typography.labelLarge.copy(
color = MaterialTheme.colorScheme.error
),
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
)
}
FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Column( Column(
modifier = Modifier modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp) .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) { ) {
Text( CircularProgressIndicator(
text = stringResource(id = R.string.settings_profile_profile_picture), modifier = Modifier
style = MaterialTheme.typography.labelLarge .size(48.dp)
) )
}
Spacer(Modifier.height(10.dp)) } else {
RevoltAPI.userCache[RevoltAPI.selfId]?.let {
InlineMediaPicker( RawUserOverview(
currentModel = viewModel.pfpModel, it,
circular = true, viewModel.pendingProfile,
onPick = { viewModel.pfpModel?.toString(),
viewModel.pfpModel = it.toString() viewModel.backgroundModel?.toString()
viewModel.saveNewPfp()
},
canRemove = true,
onRemove = {
viewModel.removePfp()
}
) )
} }
Column( AnimatedVisibility(visible = viewModel.uploadProgress > 0f) {
modifier = Modifier LinearProgressIndicator(
.padding(20.dp) progress = viewModel.uploadProgress,
) { modifier = Modifier
Text( .fillMaxSize()
text = stringResource(id = R.string.settings_profile_custom_background), .padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
style = MaterialTheme.typography.labelLarge,
)
Spacer(Modifier.height(10.dp))
InlineMediaPicker(
currentModel = viewModel.backgroundModel,
onPick = {
viewModel.backgroundModel = it.toString()
viewModel.saveNewBackground()
},
canRemove = true,
onRemove = {
viewModel.removeBackground()
}
) )
} }
}
Column( AnimatedVisibility(visible = viewModel.uploadError != null) {
modifier = Modifier Text(
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 20.dp) text = viewModel.uploadError ?: "",
) { style = MaterialTheme.typography.labelLarge.copy(
OutlinedTextField( color = MaterialTheme.colorScheme.error
value = viewModel.pendingProfile?.content ?: "", ),
onValueChange = { value -> modifier = Modifier
viewModel.pendingProfile?.let { .padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
viewModel.pendingProfile = it.copy(content = value) )
} }
},
label = { FlowRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Column(
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
) {
Text( Text(
text = stringResource(id = R.string.user_info_sheet_category_bio), text = stringResource(id = R.string.settings_profile_profile_picture),
style = MaterialTheme.typography.labelLarge
)
Spacer(Modifier.height(10.dp))
InlineMediaPicker(
currentModel = viewModel.pfpModel,
circular = true,
onPick = {
viewModel.pfpModel = it.toString()
viewModel.saveNewPfp()
},
canRemove = true,
onRemove = {
viewModel.removePfp()
}
)
}
Column(
modifier = Modifier
.padding(20.dp)
) {
Text(
text = stringResource(id = R.string.settings_profile_custom_background),
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
) )
},
)
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(10.dp))
TextButton( InlineMediaPicker(
onClick = { currentModel = viewModel.backgroundModel,
viewModel.saveBio() onPick = {
}, viewModel.backgroundModel = it.toString()
enabled = viewModel.pendingProfile?.content != viewModel.currentProfile?.content, viewModel.saveNewBackground()
modifier = Modifier.fillMaxWidth() },
canRemove = true,
onRemove = {
viewModel.removeBackground()
}
)
}
}
Column(
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 0.dp, bottom = 20.dp)
) { ) {
Icon( OutlinedTextField(
imageVector = Icons.Default.Check, value = viewModel.pendingProfile?.content ?: "",
contentDescription = null onValueChange = { value ->
viewModel.pendingProfile?.let {
viewModel.pendingProfile = it.copy(content = value)
}
},
label = {
Text(
text = stringResource(id = R.string.user_info_sheet_category_bio),
style = MaterialTheme.typography.labelLarge,
)
},
) )
Spacer(modifier = Modifier.width(8.dp)) AnimatedVisibility(visible = viewModel.bioError != null) {
Spacer(Modifier.height(8.dp))
Text(
text = viewModel.bioError ?: "",
style = MaterialTheme.typography.labelLarge.copy(
color = MaterialTheme.colorScheme.error
),
modifier = Modifier
.padding(start = 20.dp, end = 20.dp, top = 20.dp, bottom = 0.dp)
)
}
Text( Spacer(Modifier.height(8.dp))
text = stringResource(id = R.string.settings_profile_save),
style = MaterialTheme.typography.bodySmall TextButton(
) onClick = {
viewModel.saveBio()
},
enabled = viewModel.pendingProfile?.content != viewModel.currentProfile?.content,
modifier = Modifier.fillMaxWidth()
) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.settings_profile_save),
style = MaterialTheme.typography.bodySmall
)
}
} }
} }
} }