From e35cb17879b901691c74d4112123f86c66fb4916 Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Thu, 23 Jun 2022 17:11:11 +0200
Subject: [PATCH 1/2] start on profile editing functionality
---
app/src/main/AndroidManifest.xml | 4 +-
.../app/profile/EditProfileActivity.kt | 71 ++++++++++
.../app/profile/EditProfileViewModel.kt | 129 ++++++++++++++++++
.../pixeldroid/app/profile/ProfileActivity.kt | 8 +-
.../pixeldroid/app/utils/api/PixelfedAPI.kt | 8 ++
.../pixeldroid/app/utils/api/objects/Field.kt | 9 +-
.../app/utils/api/objects/Source.kt | 14 +-
.../app/utils/di/ApplicationComponent.kt | 2 +
app/src/main/res/drawable/done.xml | 5 +
.../main/res/layout/activity_edit_profile.xml | 69 ++++++++++
app/src/main/res/layout/activity_login.xml | 3 +-
app/src/main/res/menu/edit_profile_menu.xml | 11 ++
app/src/main/res/values/strings.xml | 1 +
13 files changed, 324 insertions(+), 10 deletions(-)
create mode 100644 app/src/main/java/org/pixeldroid/app/profile/EditProfileActivity.kt
create mode 100644 app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt
create mode 100644 app/src/main/res/drawable/done.xml
create mode 100644 app/src/main/res/layout/activity_edit_profile.xml
create mode 100644 app/src/main/res/menu/edit_profile_menu.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 128d4462..b77f5f17 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -31,7 +31,9 @@
android:name=".posts.AlbumActivity"
android:exported="false"
android:theme="@style/AppTheme.ActionBar.Transparent"/>
-
+
+ binding.savingProgressBar.visibility = if(uiState.loadingProfile) View.VISIBLE else View.INVISIBLE
+ binding.bioEditText.setText(uiState.bio)
+ binding.nameEditText.setText(uiState.name)
+ Glide.with(binding.profilePic).load(uiState.profilePictureUri)
+ .apply(RequestOptions.circleCropTransform())
+ .into(binding.profilePic)
+ if(uiState.error){
+ Snackbar.make(binding.root, "Something went wrong",
+ Snackbar.LENGTH_LONG).show()
+ model.errorShown()
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.edit_profile_menu, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId){
+ R.id.action_apply -> {
+ model.apply(
+ binding.nameEditText.text.toString(),
+ binding.bioEditText.text.toString(),
+ )
+ return true
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+}
diff --git a/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt b/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt
new file mode 100644
index 00000000..a225d398
--- /dev/null
+++ b/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt
@@ -0,0 +1,129 @@
+package org.pixeldroid.app.profile
+
+import android.app.Application
+import android.net.Uri
+import android.util.Log
+import androidx.core.net.toUri
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.toRequestBody
+import org.pixeldroid.app.utils.PixelDroidApplication
+import org.pixeldroid.app.utils.api.objects.Account
+import org.pixeldroid.app.utils.di.PixelfedAPIHolder
+import retrofit2.HttpException
+import java.io.IOException
+import java.lang.Exception
+import javax.inject.Inject
+
+class EditProfileViewModel(application: Application) : AndroidViewModel(application) {
+
+ @Inject
+ lateinit var apiHolder: PixelfedAPIHolder
+
+ private val _uiState = MutableStateFlow(EditProfileActivityUiState())
+ val uiState: StateFlow = _uiState
+
+ var oldProfile: Account? = null
+
+ init {
+ (application as PixelDroidApplication).getAppComponent().inject(this)
+ loadProfile()
+ }
+
+ private fun loadProfile() {
+ viewModelScope.launch {
+ val api = apiHolder.api ?: apiHolder.setToCurrentUser()
+ try {
+ oldProfile = api.verifyCredentials()
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ name = oldProfile?.display_name,
+ bio = oldProfile?.source?.note,
+ profilePictureUri = oldProfile?.anyAvatar()?.toUri(),
+ privateAccount = oldProfile?.locked,
+ loadingProfile = false,
+ sendingProfile = false
+ )
+ }
+ } catch (exception: IOException) {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ )
+ }
+ } catch (exception: HttpException) {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ )
+ }
+ }
+ }
+ }
+
+ fun apply(name: String, bio: String) {
+ //TODO check if name and bio have changed, else send null to updatecredentials or don't update at all
+ _uiState.update { currentUiState ->
+ if(oldProfile != null) currentUiState.copy(name = name, bio = bio, sendingProfile = true, loadingProfile = false)
+ else currentUiState.copy(name = name, bio = bio, sendingProfile = false)
+ }
+ if(oldProfile == null) return
+
+ val api = apiHolder.api ?: apiHolder.setToCurrentUser()
+
+ val requestBody = null //MultipartBody.Part.createFormData("avatar", System.currentTimeMillis().toString(), avatarBody)
+
+ viewModelScope.launch {
+ with(uiState.value) {
+ try {
+ api.updateCredentials(
+ displayName = name,
+ note = bio,
+ locked = privateAccount,
+ // avatar = requestBody
+ )
+ } catch (exception: IOException) {
+ Log.e("TAG", exception.toString())
+ _uiState.update { currentUiState ->
+ currentUiState.copy(error = true)
+ }
+ } catch (exception: HttpException) {
+ Log.e("TAG", exception.toString())
+ _uiState.update { currentUiState ->
+ currentUiState.copy(error = true)
+ }
+ } catch (exception: Exception) {
+ Log.e("TAG", exception.toString())
+
+ }
+ }
+ }
+ }
+
+ fun errorShown() {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(error = false)
+ }
+ }
+}
+
+data class EditProfileActivityUiState(
+ val name: String? = null,
+ val bio: String? = null,
+ val profilePictureUri: Uri?= null,
+ val privateAccount: Boolean? = null,
+ val loadingProfile: Boolean = true,
+ val sendingProfile: Boolean = false,
+ val error: Boolean = false
+)
+
+class EditProfileViewModelFactory(val application: Application) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ return modelClass.getConstructor(Application::class.java).newInstance(application)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/pixeldroid/app/profile/ProfileActivity.kt b/app/src/main/java/org/pixeldroid/app/profile/ProfileActivity.kt
index d85b9a08..cedc21fb 100644
--- a/app/src/main/java/org/pixeldroid/app/profile/ProfileActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/profile/ProfileActivity.kt
@@ -228,12 +228,8 @@ class ProfileActivity : BaseThemedWithBarActivity() {
}
private fun onClickEditButton() {
- val url = "$domain/settings/home"
-
- if(!openUrl(url)) {
- Snackbar.make(binding.root, getString(R.string.edit_link_failed),
- Snackbar.LENGTH_LONG).show()
- }
+ val intent = Intent(this, EditProfileActivity::class.java)
+ ContextCompat.startActivity(this, intent, null)
}
private fun onClickFollowers(account: Account?) {
diff --git a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt
index 2d8c11df..2c62f4a7 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt
@@ -295,6 +295,14 @@ interface PixelfedAPI {
@Header("Authorization") authorization: String? = null
): Account
+ @Multipart
+ @PATCH("/api/v1/accounts/update_credentials")
+ suspend fun updateCredentials(
+ @Part(value = "display_name") displayName: String?,
+ @Part(value = "note") note: String?,
+ @Part(value = "locked") locked: Boolean?,
+ // @Part avatar: MultipartBody.Part?,
+ ): Account
@GET("/api/v1/accounts/{id}/statuses")
suspend fun accountPosts(
diff --git a/app/src/main/java/org/pixeldroid/app/utils/api/objects/Field.kt b/app/src/main/java/org/pixeldroid/app/utils/api/objects/Field.kt
index f7ad2d38..711e4e59 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/api/objects/Field.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/api/objects/Field.kt
@@ -1,5 +1,12 @@
package org.pixeldroid.app.utils.api.objects
import java.io.Serializable
+import java.time.Instant
-class Field: Serializable
+data class Field(
+ //Required attributes
+ val name: String?,
+ val value: String?,
+ //Optional attributes
+ val verified_at: Instant?
+): Serializable
diff --git a/app/src/main/java/org/pixeldroid/app/utils/api/objects/Source.kt b/app/src/main/java/org/pixeldroid/app/utils/api/objects/Source.kt
index 8430be4e..0c01533e 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/api/objects/Source.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/api/objects/Source.kt
@@ -2,4 +2,16 @@ package org.pixeldroid.app.utils.api.objects
import java.io.Serializable
-class Source: Serializable
+data class Source(
+ val note: String?,
+ val fields: List?,
+ //Nullable attributes
+ val privacy: Privacy?,
+ val sensitive: Boolean?,
+ val language: String?, //ISO 639-1 language two-letter code
+ val follow_requests_count: Int?,
+): Serializable {
+ enum class Privacy: Serializable {
+ public, unlisted, private, direct
+ }
+}
diff --git a/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt b/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt
index 0a646b08..b9a779bb 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/di/ApplicationComponent.kt
@@ -8,6 +8,7 @@ import org.pixeldroid.app.utils.db.AppDatabase
import org.pixeldroid.app.utils.BaseFragment
import dagger.Component
import org.pixeldroid.app.postCreation.PostCreationViewModel
+import org.pixeldroid.app.profile.EditProfileViewModel
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
import javax.inject.Singleton
@@ -20,6 +21,7 @@ interface ApplicationComponent {
fun inject(feedFragment: BaseFragment)
fun inject(notificationsWorker: NotificationsWorker)
fun inject(postCreationViewModel: PostCreationViewModel)
+ fun inject(editProfileViewModel: EditProfileViewModel)
val context: Context?
val application: Application?
diff --git a/app/src/main/res/drawable/done.xml b/app/src/main/res/drawable/done.xml
new file mode 100644
index 00000000..2728880b
--- /dev/null
+++ b/app/src/main/res/drawable/done.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_edit_profile.xml b/app/src/main/res/layout/activity_edit_profile.xml
new file mode 100644
index 00000000..bb0467de
--- /dev/null
+++ b/app/src/main/res/layout/activity_edit_profile.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index c006d836..04b5c54d 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -33,8 +33,9 @@
android:layout_height="wrap_content"
android:gravity="center"
android:hint="@string/domain_of_your_instance"
+ android:visibility="gone"
app:errorEnabled="true"
- android:visibility="gone">
+ tools:visibility="visible">
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 30aa2c57..a24a9e03 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -309,4 +309,5 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"
- %d reply
- %d replies
+ Save
\ No newline at end of file
From 2497504530bc5f35956f42bbe7c8585b07f9e915 Mon Sep 17 00:00:00 2001
From: Matthieu <24-artectrex@users.noreply.shinice.net>
Date: Sun, 30 Oct 2022 20:51:09 +0100
Subject: [PATCH 2/2] Basic profile editing
---
app/build.gradle | 1 +
.../app/profile/EditProfileActivity.kt | 109 ++++++++-
.../app/profile/EditProfileViewModel.kt | 224 ++++++++++++++++--
.../pixeldroid/app/utils/api/PixelfedAPI.kt | 14 +-
app/src/main/res/drawable/done.xml | 4 +-
.../main/res/layout/activity_edit_profile.xml | 129 +++++++++-
app/src/main/res/layout/activity_profile.xml | 1 -
app/src/main/res/layout/color_dialog.xml | 2 +-
app/src/main/res/values/strings.xml | 12 +
gradle/verification-metadata.xml | 24 ++
10 files changed, 476 insertions(+), 44 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 7b9f597c..58c480f3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,6 +9,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco'
+
// Force latest version of Jacoco, initially done to resolve https://github.com/jacoco/jacoco/issues/1155
jacoco.toolVersion = "0.8.7"
diff --git a/app/src/main/java/org/pixeldroid/app/profile/EditProfileActivity.kt b/app/src/main/java/org/pixeldroid/app/profile/EditProfileActivity.kt
index 8970474a..b3dbcaf0 100644
--- a/app/src/main/java/org/pixeldroid/app/profile/EditProfileActivity.kt
+++ b/app/src/main/java/org/pixeldroid/app/profile/EditProfileActivity.kt
@@ -1,21 +1,26 @@
package org.pixeldroid.app.profile
+import android.app.Activity
+import android.app.AlertDialog
+import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
+import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.google.android.material.snackbar.Snackbar
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.pixeldroid.app.R
import org.pixeldroid.app.databinding.ActivityEditProfileBinding
import org.pixeldroid.app.utils.BaseActivity
+import org.pixeldroid.app.utils.openUrl
class EditProfileActivity : BaseActivity() {
@@ -35,20 +40,91 @@ class EditProfileActivity : BaseActivity() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
model.uiState.collect { uiState ->
- binding.savingProgressBar.visibility = if(uiState.loadingProfile) View.VISIBLE else View.INVISIBLE
- binding.bioEditText.setText(uiState.bio)
- binding.nameEditText.setText(uiState.name)
+ if(uiState.profileLoaded){
+ binding.bioEditText.setText(uiState.bio)
+ binding.nameEditText.setText(uiState.name)
+ model.changesApplied()
+ }
+ binding.progressCard.visibility = if(uiState.loadingProfile || uiState.sendingProfile || uiState.profileSent || uiState.error) View.VISIBLE else View.INVISIBLE
+ if(uiState.loadingProfile) binding.progressText.setText(R.string.fetching_profile)
+ else if(uiState.sendingProfile) binding.progressText.setText(R.string.saving_profile)
+ binding.privateSwitch.isChecked = uiState.privateAccount == true
Glide.with(binding.profilePic).load(uiState.profilePictureUri)
.apply(RequestOptions.circleCropTransform())
.into(binding.profilePic)
- if(uiState.error){
- Snackbar.make(binding.root, "Something went wrong",
- Snackbar.LENGTH_LONG).show()
- model.errorShown()
+
+ binding.savingProgressBar.visibility = if(uiState.error || uiState.profileSent) View.GONE
+ else View.VISIBLE
+
+ if(uiState.profileSent){
+ binding.progressText.setText(R.string.profile_saved)
+ binding.done.visibility = View.VISIBLE
+ } else {
+ binding.done.visibility = View.GONE
}
+ if(uiState.error){
+ binding.progressText.setText(R.string.error_profile)
+ binding.error.visibility = View.VISIBLE
+ } else binding.error.visibility = View.GONE
+
}
}
}
+ binding.bioEditText.doAfterTextChanged {
+ model.updateBio(binding.bioEditText.text)
+ }
+ binding.nameEditText.doAfterTextChanged {
+ model.updateName(binding.nameEditText.text)
+ }
+ binding.privateSwitch.setOnCheckedChangeListener { _, isChecked ->
+ model.updatePrivate(isChecked)
+ }
+
+ binding.progressCard.setOnClickListener {
+ model.clickedCard()
+ }
+
+ binding.editButton.setOnClickListener {
+ val domain = db.userDao().getActiveUser()!!.instance_uri
+ val url = "$domain/settings/home"
+
+ if(!openUrl(url)) {
+ Snackbar.make(binding.root, getString(R.string.edit_link_failed),
+ Snackbar.LENGTH_LONG).show()
+ }
+ }
+
+// binding.changeImageButton.setOnClickListener {
+// Intent(Intent.ACTION_GET_CONTENT).apply {
+// type = "*/*"
+// putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*"))
+// action = Intent.ACTION_GET_CONTENT
+// addCategory(Intent.CATEGORY_OPENABLE)
+// putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
+// uploadImageResultContract.launch(
+// Intent.createChooser(this, null)
+// )
+// }
+// }
+ }
+
+ private val uploadImageResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ val data: Intent? = result.data
+ if (result.resultCode == Activity.RESULT_OK && data != null) {
+ val images: ArrayList = ArrayList()
+ val clipData = data.clipData
+ if (clipData != null) {
+ val count = clipData.itemCount
+ for (i in 0 until count) {
+ val imageUri: String = clipData.getItemAt(i).uri.toString()
+ images.add(imageUri)
+ }
+ model.uploadImage(images.first())
+ } else if (data.data != null) {
+ images.add(data.data!!.toString())
+ model.uploadImage(images.first())
+ }
+ }
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -56,13 +132,22 @@ class EditProfileActivity : BaseActivity() {
return true
}
+ @Deprecated("Deprecated in Java")
+ override fun onBackPressed() {
+ if(model.madeChanges()){
+ AlertDialog.Builder(binding.root.context).apply {
+ setMessage(getString(R.string.profile_save_changes))
+ setNegativeButton(android.R.string.cancel) { _, _ -> }
+ setPositiveButton(android.R.string.ok) { _, _ -> super.onBackPressed()}
+ }.show()
+ }
+ else super.onBackPressed()
+ }
+
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId){
R.id.action_apply -> {
- model.apply(
- binding.nameEditText.text.toString(),
- binding.bioEditText.text.toString(),
- )
+ model.sendProfile()
return true
}
}
diff --git a/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt b/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt
index a225d398..f0613ec5 100644
--- a/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt
+++ b/app/src/main/java/org/pixeldroid/app/profile/EditProfileViewModel.kt
@@ -2,24 +2,38 @@ package org.pixeldroid.app.profile
import android.app.Application
import android.net.Uri
+import android.provider.OpenableColumns
+import android.text.Editable
import android.util.Log
+import android.widget.Toast
+import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
+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.MediaType
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
-import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.RequestBody
+import org.pixeldroid.app.R
+import org.pixeldroid.app.postCreation.ProgressRequestBody
import org.pixeldroid.app.utils.PixelDroidApplication
import org.pixeldroid.app.utils.api.objects.Account
+import org.pixeldroid.app.utils.api.objects.Attachment
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
import retrofit2.HttpException
+import java.io.File
import java.io.IOException
import java.lang.Exception
+import java.net.URI
import javax.inject.Inject
class EditProfileViewModel(application: Application) : AndroidViewModel(application) {
@@ -41,7 +55,8 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
viewModelScope.launch {
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
try {
- oldProfile = api.verifyCredentials()
+ val profile = api.verifyCredentials()
+ if (oldProfile == null) oldProfile = profile
_uiState.update { currentUiState ->
currentUiState.copy(
name = oldProfile?.display_name,
@@ -49,53 +64,94 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
profilePictureUri = oldProfile?.anyAvatar()?.toUri(),
privateAccount = oldProfile?.locked,
loadingProfile = false,
- sendingProfile = false
+ sendingProfile = false,
+ profileLoaded = true,
+ error = false
)
}
} catch (exception: IOException) {
_uiState.update { currentUiState ->
currentUiState.copy(
+ sendingProfile = false,
+ profileSent = false,
+ loadingProfile = false,
+ profileLoaded = false,
+ error = true
)
}
} catch (exception: HttpException) {
_uiState.update { currentUiState ->
currentUiState.copy(
+ sendingProfile = false,
+ profileSent = false,
+ loadingProfile = false,
+ profileLoaded = false,
+ error = true
)
}
}
}
}
- fun apply(name: String, bio: String) {
- //TODO check if name and bio have changed, else send null to updatecredentials or don't update at all
- _uiState.update { currentUiState ->
- if(oldProfile != null) currentUiState.copy(name = name, bio = bio, sendingProfile = true, loadingProfile = false)
- else currentUiState.copy(name = name, bio = bio, sendingProfile = false)
- }
- if(oldProfile == null) return
-
+ fun sendProfile() {
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
- val requestBody = null //MultipartBody.Part.createFormData("avatar", System.currentTimeMillis().toString(), avatarBody)
+ val requestBody =
+ null //MultipartBody.Part.createFormData("avatar", System.currentTimeMillis().toString(), avatarBody)
+
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ sendingProfile = true,
+ profileSent = false,
+ loadingProfile = false,
+ profileLoaded = false,
+ error = false
+ )
+ }
viewModelScope.launch {
with(uiState.value) {
try {
- api.updateCredentials(
+ val account = api.updateCredentials(
displayName = name,
note = bio,
locked = privateAccount,
- // avatar = requestBody
)
+ oldProfile = account
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ bio = account.note,
+ name = account.display_name,
+ profilePictureUri = account.anyAvatar()?.toUri(),
+ privateAccount = account.locked,
+ sendingProfile = false,
+ profileSent = true,
+ loadingProfile = false,
+ profileLoaded = true,
+ error = false
+ )
+ }
} catch (exception: IOException) {
Log.e("TAG", exception.toString())
_uiState.update { currentUiState ->
- currentUiState.copy(error = true)
+ currentUiState.copy(
+ sendingProfile = false,
+ profileSent = false,
+ loadingProfile = false,
+ profileLoaded = false,
+ error = true
+ )
}
} catch (exception: HttpException) {
Log.e("TAG", exception.toString())
_uiState.update { currentUiState ->
- currentUiState.copy(error = true)
+ currentUiState.copy(
+ sendingProfile = false,
+ profileSent = false,
+ loadingProfile = false,
+ profileLoaded = false,
+ error = true
+ )
}
} catch (exception: Exception) {
Log.e("TAG", exception.toString())
@@ -110,16 +166,150 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
currentUiState.copy(error = false)
}
}
+
+ 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 changesApplied() {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(profileLoaded = false)
+ }
+ }
+
+ fun madeChanges(): Boolean =
+ with(uiState.value) {
+ oldProfile?.locked != privateAccount
+ || oldProfile?.display_name != name || oldProfile?.note != bio
+ }
+
+ 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 uploadImage(image: String) {
+ //TODO fix
+ val inputStream =
+ getApplication().contentResolver.openInputStream(image.toUri())
+ ?: return
+
+ val size: Long =
+ if (image.toUri().scheme == "content") {
+ getApplication().contentResolver.query(
+ image.toUri(),
+ 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.toUri().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 inter = api.updateProfilePicture(requestBody.parts[0])
+
+ postSub = inter
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ { it: Account ->
+ Log.e("qsdfqsdfs", it.toString())
+
+ },
+ { e: Throwable ->
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ uploadProgress = 0,
+ uploadingPicture = true,
+ error = true
+ )
+ }
+ e.printStackTrace()
+ postSub?.dispose()
+ sub.dispose()
+ },
+ {
+ _uiState.update { currentUiState ->
+ currentUiState.copy(
+ uploadProgress = 100,
+ uploadingPicture = false
+ )
+ }
+ postSub?.dispose()
+ sub.dispose()
+ }
+ )
+ }
}
+
data class EditProfileActivityUiState(
val name: String? = null,
val bio: String? = null,
val profilePictureUri: Uri?= null,
val privateAccount: Boolean? = null,
val loadingProfile: Boolean = true,
+ val profileLoaded: Boolean = false,
val sendingProfile: Boolean = false,
- val error: Boolean = false
+ val profileSent: Boolean = false,
+ val error: Boolean = false,
+ val uploadingPicture: Boolean = false,
+ val uploadProgress: Int = 0,
)
class EditProfileViewModelFactory(val application: Application) : ViewModelProvider.Factory {
diff --git a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt
index 2c62f4a7..14b39e8c 100644
--- a/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt
+++ b/app/src/main/java/org/pixeldroid/app/utils/api/PixelfedAPI.kt
@@ -295,15 +295,21 @@ interface PixelfedAPI {
@Header("Authorization") authorization: String? = null
): Account
- @Multipart
+ //@Multipart
@PATCH("/api/v1/accounts/update_credentials")
suspend fun updateCredentials(
- @Part(value = "display_name") displayName: String?,
- @Part(value = "note") note: String?,
- @Part(value = "locked") locked: Boolean?,
+ @Query(value = "display_name") displayName: String?,
+ @Query(value = "note") note: String?,
+ @Query(value = "locked") locked: Boolean?,
// @Part avatar: MultipartBody.Part?,
): Account
+ @Multipart
+ @PATCH("/api/v1/accounts/update_credentials")
+ fun updateProfilePicture(
+ @Part avatar: MultipartBody.Part?
+ ): Observable
+
@GET("/api/v1/accounts/{id}/statuses")
suspend fun accountPosts(
@Path("id") account_id: String,
diff --git a/app/src/main/res/drawable/done.xml b/app/src/main/res/drawable/done.xml
index 2728880b..399ee2df 100644
--- a/app/src/main/res/drawable/done.xml
+++ b/app/src/main/res/drawable/done.xml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/app/src/main/res/layout/activity_edit_profile.xml b/app/src/main/res/layout/activity_edit_profile.xml
index bb0467de..27f23e6c 100644
--- a/app/src/main/res/layout/activity_edit_profile.xml
+++ b/app/src/main/res/layout/activity_edit_profile.xml
@@ -14,7 +14,9 @@
app:layout_constraintBottom_toTopOf="@+id/textInputLayoutName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- tools:srcCompat="@tools:sample/avatars" />
+ tools:srcCompat="@tools:sample/avatars"
+ android:contentDescription="@string/profile_picture" />
+
@@ -53,17 +55,130 @@
android:id="@+id/bioEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="Add a bio here" />
+ android:hint="@string/your_bio" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ app:layout_constraintTop_toTopOf="parent" >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml
index 9fa98d44..08300107 100644
--- a/app/src/main/res/layout/activity_profile.xml
+++ b/app/src/main/res/layout/activity_profile.xml
@@ -116,7 +116,6 @@
android:layout_height="wrap_content"
android:text="@string/edit_profile"
android:visibility="gone"
- app:icon="@drawable/ic_baseline_open_in_browser_24"
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
diff --git a/app/src/main/res/layout/color_dialog.xml b/app/src/main/res/layout/color_dialog.xml
index 1ac31c4d..a82d3cbc 100644
--- a/app/src/main/res/layout/color_dialog.xml
+++ b/app/src/main/res/layout/color_dialog.xml
@@ -11,7 +11,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:lineSpacingExtra="8sp"
- android:text="Use dynamic color from your system"
+ android:text="@string/use_dynamic_color"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a24a9e03..d5dfbe29 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -310,4 +310,16 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"
- %d replies
Save
+ Use dynamic color from your system
+ More profile settings
+ Private Account
+ When your account is private, only people you approve can see your photos and videos on pixelfed. Your existing followers won\'t be affected.
+ Your bio
+ Your Name
+ You did not save your changes. Exit?
+ Fetching your profile...
+ Saving your profile
+ Changes saved!
+ Something went wrong. Tap to retry
+ Change your profile picture
\ No newline at end of file
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index 7da4b397..54bc404e 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -8331,6 +8331,14 @@
+
+
+
+
+
+
+
+
@@ -8756,6 +8764,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+