Properly handle more than 4 fields in EditProfileViewModel (#4936)
Also read `configuration.accounts.max_profile_fields` from `api/v2/instance` to get the correct limit for GoToSocial. Glitch-soc also allows more fields but does not provide configuration yet, see https://github.com/glitch-soc/mastodon/issues/2973 closes https://github.com/tuskyapp/Tusky/issues/3305
This commit is contained in:
parent
cd24ee8f26
commit
1157be18cf
5 changed files with 42 additions and 57 deletions
|
|
@ -170,7 +170,7 @@ class InstanceInfoRepository @Inject constructor(
|
|||
?: DEFAULT_IMAGE_MATRIX_LIMIT,
|
||||
maxMediaAttachments = this.configuration?.statuses?.maxMediaAttachments
|
||||
?: DEFAULT_MAX_MEDIA_ATTACHMENTS,
|
||||
maxFields = this.pleroma?.metadata?.fieldLimits?.maxFields,
|
||||
maxFields = this.configuration?.accounts?.maxProfileFields ?: this.pleroma?.metadata?.fieldLimits?.maxFields,
|
||||
maxFieldNameLength = this.pleroma?.metadata?.fieldLimits?.nameLength,
|
||||
maxFieldValueLength = this.pleroma?.metadata?.fieldLimits?.valueLength,
|
||||
translationEnabled = this.configuration?.translation?.enabled
|
||||
|
|
|
|||
|
|
@ -51,7 +51,11 @@ data class Instance(
|
|||
data class Urls(@Json(name = "streaming_api") val streamingApi: String? = null)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Accounts(@Json(name = "max_featured_tags") val maxFeaturedTags: Int)
|
||||
data class Accounts(
|
||||
@Json(name = "max_featured_tags") val maxFeaturedTags: Int,
|
||||
// GoToSocial feature
|
||||
@Json(name = "max_profile_fields") val maxProfileFields: Int?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Statuses(
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ import retrofit2.http.PATCH
|
|||
import retrofit2.http.POST
|
||||
import retrofit2.http.PUT
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PartMap
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
|
@ -314,14 +315,7 @@ interface MastodonApi {
|
|||
@Part(value = "locked") locked: RequestBody?,
|
||||
@Part avatar: MultipartBody.Part?,
|
||||
@Part header: MultipartBody.Part?,
|
||||
@Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?,
|
||||
@Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?,
|
||||
@Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?,
|
||||
@Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?,
|
||||
@Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?,
|
||||
@Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?,
|
||||
@Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?,
|
||||
@Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody?
|
||||
@PartMap fields: Map<String, RequestBody>
|
||||
): NetworkResult<Account>
|
||||
|
||||
@GET("api/v1/accounts/search")
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.keylesspalace.tusky.viewmodel
|
|||
|
||||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -139,38 +140,38 @@ class EditProfileViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
var avatarFileBody: MultipartBody.Part? = null
|
||||
diff.avatarFile?.let {
|
||||
avatarFileBody = MultipartBody.Part.createFormData(
|
||||
val avatarFileBody: MultipartBody.Part? = diff.avatarFile?.let {
|
||||
MultipartBody.Part.createFormData(
|
||||
"avatar",
|
||||
randomAlphanumericString(12),
|
||||
it.asRequestBody("image/png".toMediaTypeOrNull())
|
||||
)
|
||||
}
|
||||
|
||||
var headerFileBody: MultipartBody.Part? = null
|
||||
diff.headerFile?.let {
|
||||
headerFileBody = MultipartBody.Part.createFormData(
|
||||
val headerFileBody: MultipartBody.Part? = diff.headerFile?.let {
|
||||
MultipartBody.Part.createFormData(
|
||||
"header",
|
||||
randomAlphanumericString(12),
|
||||
it.asRequestBody("image/png".toMediaTypeOrNull())
|
||||
)
|
||||
}
|
||||
|
||||
val fieldsMap = diff.fields?.let { fields ->
|
||||
buildMap {
|
||||
fields.forEachIndexed { index, field ->
|
||||
put("fields_attributes[$index][name]", field.name.toRequestBody(MultipartBody.FORM))
|
||||
put("fields_attributes[$index][value]", field.value.toRequestBody(MultipartBody.FORM))
|
||||
}
|
||||
}
|
||||
}.orEmpty()
|
||||
|
||||
mastodonApi.accountUpdateCredentials(
|
||||
diff.displayName?.toRequestBody(MultipartBody.FORM),
|
||||
diff.note?.toRequestBody(MultipartBody.FORM),
|
||||
diff.locked?.toString()?.toRequestBody(MultipartBody.FORM),
|
||||
avatarFileBody,
|
||||
headerFileBody,
|
||||
diff.field1?.first?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field1?.second?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field2?.first?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field2?.second?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field3?.first?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field3?.second?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field4?.first?.toRequestBody(MultipartBody.FORM),
|
||||
diff.field4?.second?.toRequestBody(MultipartBody.FORM)
|
||||
displayName = diff.displayName?.toRequestBody(MultipartBody.FORM),
|
||||
note = diff.note?.toRequestBody(MultipartBody.FORM),
|
||||
locked = diff.locked?.toString()?.toRequestBody(MultipartBody.FORM),
|
||||
avatar = avatarFileBody,
|
||||
header = headerFileBody,
|
||||
fields = fieldsMap
|
||||
).fold(
|
||||
{ newAccountData ->
|
||||
accountManager.updateAccount(activeAccount, newAccountData)
|
||||
|
|
@ -178,6 +179,7 @@ class EditProfileViewModel @Inject constructor(
|
|||
_saveData.value = Success()
|
||||
},
|
||||
{ throwable ->
|
||||
Log.d(TAG, "failed updating profile", throwable)
|
||||
_saveData.value = Error(errorMessage = throwable.getServerErrorMessage())
|
||||
}
|
||||
)
|
||||
|
|
@ -236,28 +238,13 @@ class EditProfileViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
// when one field changed, all have to be sent or they unchanged ones would get overridden
|
||||
val allFieldsUnchanged = oldProfileAccount?.source?.fields == newProfileData.fields
|
||||
val field1 = calculateFieldToUpdate(newProfileData.fields.getOrNull(0), allFieldsUnchanged)
|
||||
val field2 = calculateFieldToUpdate(newProfileData.fields.getOrNull(1), allFieldsUnchanged)
|
||||
val field3 = calculateFieldToUpdate(newProfileData.fields.getOrNull(2), allFieldsUnchanged)
|
||||
val field4 = calculateFieldToUpdate(newProfileData.fields.getOrNull(3), allFieldsUnchanged)
|
||||
|
||||
return DiffProfileData(
|
||||
displayName, note, locked, field1, field2, field3, field4, headerFile, avatarFile
|
||||
)
|
||||
}
|
||||
|
||||
private fun calculateFieldToUpdate(
|
||||
newField: StringField?,
|
||||
fieldsUnchanged: Boolean
|
||||
): Pair<String, String>? {
|
||||
if (fieldsUnchanged || newField == null) {
|
||||
return null
|
||||
val fields = if (oldProfileAccount?.source?.fields == newProfileData.fields) {
|
||||
null
|
||||
} else {
|
||||
newProfileData.fields
|
||||
}
|
||||
return Pair(
|
||||
newField.name,
|
||||
newField.value
|
||||
)
|
||||
|
||||
return DiffProfileData(displayName, note, locked, fields, headerFile, avatarFile)
|
||||
}
|
||||
|
||||
private fun getCacheFileForName(filename: String): File {
|
||||
|
|
@ -268,15 +255,15 @@ class EditProfileViewModel @Inject constructor(
|
|||
val displayName: String?,
|
||||
val note: String?,
|
||||
val locked: Boolean?,
|
||||
val field1: Pair<String, String>?,
|
||||
val field2: Pair<String, String>?,
|
||||
val field3: Pair<String, String>?,
|
||||
val field4: Pair<String, String>?,
|
||||
val fields: List<StringField>?,
|
||||
val headerFile: File?,
|
||||
val avatarFile: File?
|
||||
) {
|
||||
fun hasChanges() = displayName != null || note != null || locked != null ||
|
||||
avatarFile != null || headerFile != null || field1 != null || field2 != null ||
|
||||
field3 != null || field4 != null
|
||||
avatarFile != null || headerFile != null || fields != null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "EditProfileViewModel"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -599,7 +599,7 @@ class ComposeActivityTest {
|
|||
private fun getConfiguration(maximumStatusCharacters: Int?, charactersReservedPerUrl: Int?): Instance.Configuration {
|
||||
return Instance.Configuration(
|
||||
Instance.Configuration.Urls(),
|
||||
Instance.Configuration.Accounts(1),
|
||||
Instance.Configuration.Accounts(maxFeaturedTags = 1, maxProfileFields = 4),
|
||||
Instance.Configuration.Statuses(
|
||||
maximumStatusCharacters ?: InstanceInfoRepository.DEFAULT_CHARACTER_LIMIT,
|
||||
InstanceInfoRepository.DEFAULT_MAX_MEDIA_ATTACHMENTS,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue