start on profile editing functionality
This commit is contained in:
parent
5b3870b80d
commit
e35cb17879
@ -31,7 +31,9 @@
|
||||
android:name=".posts.AlbumActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/AppTheme.ActionBar.Transparent"/>
|
||||
|
||||
<activity
|
||||
android:name=".profile.EditProfileActivity"
|
||||
android:exported="false"/>
|
||||
<activity
|
||||
android:name=".posts.MediaViewerActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
|
@ -0,0 +1,71 @@
|
||||
package org.pixeldroid.app.profile
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
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
|
||||
|
||||
class EditProfileActivity : BaseActivity() {
|
||||
|
||||
private lateinit var model: EditProfileViewModel
|
||||
private lateinit var binding: ActivityEditProfileBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityEditProfileBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setTitle(R.string.edit_profile)
|
||||
|
||||
val _model: EditProfileViewModel by viewModels { EditProfileViewModelFactory(application) }
|
||||
model = _model
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
@ -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<EditProfileActivityUiState> = _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 <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.getConstructor(Application::class.java).newInstance(application)
|
||||
}
|
||||
}
|
@ -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?) {
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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<Field>?,
|
||||
//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
|
||||
}
|
||||
}
|
||||
|
@ -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?
|
||||
|
5
app/src/main/res/drawable/done.xml
Normal file
5
app/src/main/res/drawable/done.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||
</vector>
|
69
app/src/main/res/layout/activity_edit_profile.xml
Normal file
69
app/src/main/res/layout/activity_edit_profile.xml
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/profilePic"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textInputLayoutName"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@tools:sample/avatars" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutName"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/profilePic">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/nameEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Your Name"
|
||||
android:ems="10"
|
||||
android:imeOptions="actionDone" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayoutBio"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textInputLayoutName">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/bioEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Add a bio here" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/savingProgressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -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">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editText"
|
||||
|
11
app/src/main/res/menu/edit_profile_menu.xml
Normal file
11
app/src/main/res/menu/edit_profile_menu.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_apply"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/save"
|
||||
android:icon="@drawable/done"
|
||||
app:showAsAction="ifRoom"/>
|
||||
</menu>
|
@ -309,4 +309,5 @@ For more info about Pixelfed, you can check here: https://pixelfed.org"</string>
|
||||
<item quantity="one">%d reply</item>
|
||||
<item quantity="other">%d replies</item>
|
||||
</plurals>
|
||||
<string name="save">Save</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user