307 lines
11 KiB
Kotlin
307 lines
11 KiB
Kotlin
package org.pixeldroid.app.profile
|
|
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import android.provider.OpenableColumns
|
|
import android.text.Editable
|
|
import android.util.Log
|
|
import androidx.core.net.toFile
|
|
import androidx.core.net.toUri
|
|
import androidx.lifecycle.ViewModel
|
|
import androidx.lifecycle.viewModelScope
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
import io.reactivex.rxjava3.disposables.Disposable
|
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
import kotlinx.coroutines.flow.update
|
|
import kotlinx.coroutines.launch
|
|
import okhttp3.MultipartBody
|
|
import org.pixeldroid.app.postCreation.ProgressRequestBody
|
|
import org.pixeldroid.app.posts.fromHtml
|
|
import org.pixeldroid.app.utils.api.objects.Account
|
|
import org.pixeldroid.app.utils.db.AppDatabase
|
|
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
|
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
|
import retrofit2.HttpException
|
|
import javax.inject.Inject
|
|
|
|
@HiltViewModel
|
|
class EditProfileViewModel @Inject constructor(
|
|
@ApplicationContext private val applicationContext: Context
|
|
): ViewModel() {
|
|
|
|
@Inject
|
|
lateinit var apiHolder: PixelfedAPIHolder
|
|
|
|
@Inject
|
|
lateinit var db: AppDatabase
|
|
|
|
private val _uiState = MutableStateFlow(EditProfileActivityUiState())
|
|
val uiState: StateFlow<EditProfileActivityUiState> = _uiState
|
|
|
|
private var oldProfile: Account? = null
|
|
|
|
var submittedChanges = false
|
|
private set
|
|
|
|
init {
|
|
loadProfile()
|
|
}
|
|
|
|
private fun loadProfile() {
|
|
viewModelScope.launch {
|
|
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
|
try {
|
|
val profile = api.verifyCredentials()
|
|
updateUserInfoDb(db, profile)
|
|
if (oldProfile == null) oldProfile = profile
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
name = oldProfile?.display_name,
|
|
bio = oldProfile?.source?.note,
|
|
profilePictureUri = oldProfile?.anyAvatar()?.toUri(),
|
|
privateAccount = oldProfile?.locked,
|
|
loadingProfile = false,
|
|
sendingProfile = false,
|
|
profileLoaded = true,
|
|
error = false
|
|
)
|
|
}
|
|
} catch (exception: Exception) {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
sendingProfile = false,
|
|
profileSent = false,
|
|
loadingProfile = false,
|
|
profileLoaded = false,
|
|
error = true
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun sendProfile() {
|
|
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
|
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
sendingProfile = true,
|
|
profileSent = false,
|
|
error = false
|
|
)
|
|
}
|
|
|
|
viewModelScope.launch {
|
|
with(uiState.value) {
|
|
try {
|
|
val account = api.updateCredentials(
|
|
displayName = name,
|
|
note = bio,
|
|
locked = privateAccount,
|
|
)
|
|
if (madeChanges()) submittedChanges = true
|
|
oldProfile = account
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
bio = account.source?.note
|
|
?: account.note?.let { fromHtml(it).toString() },
|
|
name = account.display_name,
|
|
profilePictureUri = if (profilePictureChanged) profilePictureUri
|
|
else account.anyAvatar()?.toUri(),
|
|
uploadProgress = 0,
|
|
uploadingPicture = profilePictureChanged,
|
|
privateAccount = account.locked,
|
|
sendingProfile = false,
|
|
profileSent = true,
|
|
loadingProfile = false,
|
|
profileLoaded = true,
|
|
error = false
|
|
)
|
|
}
|
|
if(profilePictureChanged) uploadImage()
|
|
} catch (exception: Exception) {
|
|
Log.e("TAG", exception.toString())
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
sendingProfile = false,
|
|
profileSent = false,
|
|
error = true
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun updateBio(bio: Editable?) {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(bio = bio.toString())
|
|
}
|
|
}
|
|
|
|
fun updateName(name: Editable?) {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(name = name.toString())
|
|
}
|
|
}
|
|
|
|
fun updatePrivate(isChecked: Boolean) {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(privateAccount = isChecked)
|
|
}
|
|
}
|
|
|
|
fun madeChanges(): Boolean =
|
|
with(uiState.value) {
|
|
val privateChanged = oldProfile?.locked != privateAccount
|
|
val displayNameChanged = oldProfile?.display_name != name
|
|
val bioChanged: Boolean = oldProfile?.source?.note?.let { it != bio }
|
|
// If source note is null, check note
|
|
?: oldProfile?.note?.let { fromHtml(it).toString() != bio }
|
|
?: true
|
|
|
|
profilePictureChanged || privateChanged || displayNameChanged || bioChanged
|
|
}
|
|
|
|
fun clickedCard() {
|
|
if (uiState.value.error) {
|
|
if (!uiState.value.profileLoaded) {
|
|
// Load failed
|
|
loadProfile()
|
|
} else if (uiState.value.profileLoaded) {
|
|
// Send failed
|
|
sendProfile()
|
|
}
|
|
} else {
|
|
// Dismiss success card
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(profileSent = false)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun updateImage(image: String) {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
profilePictureUri = image.toUri(),
|
|
profilePictureChanged = true,
|
|
profileSent = false
|
|
)
|
|
}
|
|
}
|
|
|
|
private fun uploadImage() {
|
|
val image = uiState.value.profilePictureUri!!
|
|
|
|
val inputStream =
|
|
applicationContext.contentResolver.openInputStream(image)
|
|
?: return
|
|
|
|
val size: Long =
|
|
if (image.scheme == "content") {
|
|
applicationContext.contentResolver.query(
|
|
image,
|
|
null,
|
|
null,
|
|
null,
|
|
null
|
|
)
|
|
?.use { cursor ->
|
|
/* Get the column indexes of the data in the Cursor,
|
|
* move to the first row in the Cursor, get the data,
|
|
* and display it.
|
|
*/
|
|
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
|
cursor.moveToFirst()
|
|
cursor.getLong(sizeIndex)
|
|
} ?: 0
|
|
} else {
|
|
image.toFile().length()
|
|
}
|
|
|
|
val imagePart = ProgressRequestBody(inputStream, size, "image/*")
|
|
|
|
val requestBody = MultipartBody.Builder()
|
|
.setType(MultipartBody.FORM)
|
|
.addFormDataPart("avatar", System.currentTimeMillis().toString(), imagePart)
|
|
.build()
|
|
val sub = imagePart.progressSubject
|
|
.subscribeOn(Schedulers.io())
|
|
.subscribe { percentage ->
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
uploadProgress = percentage.toInt()
|
|
)
|
|
}
|
|
}
|
|
|
|
var postSub: Disposable? = null
|
|
|
|
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
|
|
|
val pixelfed = db.instanceDao().getActiveInstance().pixelfed
|
|
|
|
val inter =
|
|
if(pixelfed) api.updateProfilePicture(requestBody.parts[0])
|
|
else api.updateProfilePictureMastodon(requestBody.parts[0])
|
|
|
|
postSub = inter
|
|
.subscribeOn(Schedulers.io())
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.subscribe(
|
|
/* onNext = */ { account: Account ->
|
|
account.anyAvatar()?.let {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
profilePictureUri = it.toUri()
|
|
)
|
|
}
|
|
}
|
|
},
|
|
/* onError = */ { e: Throwable ->
|
|
Log.e("error", (e as? HttpException)?.message().orEmpty())
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
uploadProgress = 0,
|
|
uploadingPicture = false,
|
|
error = true
|
|
)
|
|
}
|
|
e.printStackTrace()
|
|
postSub?.dispose()
|
|
sub.dispose()
|
|
},
|
|
/* onComplete = */ {
|
|
_uiState.update { currentUiState ->
|
|
currentUiState.copy(
|
|
profilePictureChanged = false,
|
|
uploadProgress = 100,
|
|
uploadingPicture = false
|
|
)
|
|
}
|
|
postSub?.dispose()
|
|
sub.dispose()
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
data class EditProfileActivityUiState(
|
|
val name: String? = null,
|
|
val bio: String? = null,
|
|
val profilePictureUri: Uri? = null,
|
|
val profilePictureChanged: Boolean = false,
|
|
val privateAccount: Boolean? = null,
|
|
val loadingProfile: Boolean = true,
|
|
val profileLoaded: Boolean = false,
|
|
val sendingProfile: Boolean = false,
|
|
val profileSent: Boolean = false,
|
|
val error: Boolean = false,
|
|
val uploadingPicture: Boolean = false,
|
|
val uploadProgress: Int = 0,
|
|
) |