Merge branch 'profileEdit' into 'master'
Edit profile in-app See merge request pixeldroid/PixelDroid!434
This commit is contained in:
commit
2d712ed395
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,156 @@
|
|||
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.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() {
|
||||
|
||||
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 ->
|
||||
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)
|
||||
|
||||
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<String> = 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 {
|
||||
menuInflater.inflate(R.menu.edit_profile_menu, menu)
|
||||
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.sendProfile()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
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
|
||||
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) {
|
||||
|
||||
@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 {
|
||||
val profile = api.verifyCredentials()
|
||||
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: 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 sendProfile() {
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
|
||||
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 {
|
||||
val account = api.updateCredentials(
|
||||
displayName = name,
|
||||
note = bio,
|
||||
locked = privateAccount,
|
||||
)
|
||||
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(
|
||||
sendingProfile = false,
|
||||
profileSent = false,
|
||||
loadingProfile = false,
|
||||
profileLoaded = false,
|
||||
error = true
|
||||
)
|
||||
}
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("TAG", exception.toString())
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
sendingProfile = false,
|
||||
profileSent = false,
|
||||
loadingProfile = false,
|
||||
profileLoaded = false,
|
||||
error = true
|
||||
)
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e("TAG", exception.toString())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun errorShown() {
|
||||
_uiState.update { currentUiState ->
|
||||
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<PixelDroidApplication>().contentResolver.openInputStream(image.toUri())
|
||||
?: return
|
||||
|
||||
val size: Long =
|
||||
if (image.toUri().scheme == "content") {
|
||||
getApplication<PixelDroidApplication>().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 profileSent: Boolean = false,
|
||||
val error: Boolean = false,
|
||||
val uploadingPicture: Boolean = false,
|
||||
val uploadProgress: Int = 0,
|
||||
)
|
||||
|
||||
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,20 @@ interface PixelfedAPI {
|
|||
@Header("Authorization") authorization: String? = null
|
||||
): Account
|
||||
|
||||
//@Multipart
|
||||
@PATCH("/api/v1/accounts/update_credentials")
|
||||
suspend fun updateCredentials(
|
||||
@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<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?
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/colorOnPrimaryContainer" android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
|
||||
</vector>
|
|
@ -0,0 +1,184 @@
|
|||
<?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"
|
||||
android:contentDescription="@string/profile_picture" />
|
||||
|
||||
|
||||
<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="@string/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="@string/your_bio" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/privateSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/privateText"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textInputLayoutBio" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/privateText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/privateSwitch"
|
||||
app:layout_constraintTop_toTopOf="@+id/privateSwitch"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/privateSwitch">
|
||||
<TextView
|
||||
android:id="@+id/privateTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/private_account"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/private_account_explanation"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/privateTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/privateTitle" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/editButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/more_profile_settings"
|
||||
android:layout_marginTop="24dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:icon="@drawable/ic_baseline_open_in_browser_24"
|
||||
app:layout_constraintTop_toBottomOf="@+id/privateText" />
|
||||
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/progressCard"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
style="?attr/materialCardViewElevatedStyle"
|
||||
app:cardBackgroundColor="?attr/colorSecondaryContainer"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" >
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_margin="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/progressIcon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
<ProgressBar
|
||||
android:id="@+id/savingProgressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/error"
|
||||
app:tint="?attr/colorOnSecondaryContainer"
|
||||
android:src="@drawable/error"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/profile_saved" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/done"
|
||||
android:src="@drawable/check_circle_24"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:contentDescription="@string/profile_saved" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<TextView
|
||||
android:id="@+id/progressText"
|
||||
tools:text="@string/fetching_profile"
|
||||
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_toBottomOf="@+id/progressIcon"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,17 @@ 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>
|
||||
<string name="use_dynamic_color">Use dynamic color from your system</string>
|
||||
<string name="more_profile_settings">More profile settings</string>
|
||||
<string name="private_account">Private Account</string>
|
||||
<string name="private_account_explanation">When your account is private, only people you approve can see your photos and videos on pixelfed. Your existing followers won\'t be affected.</string>
|
||||
<string name="your_bio">Your bio</string>
|
||||
<string name="your_name">Your Name</string>
|
||||
<string name="profile_save_changes">You did not save your changes. Exit?</string>
|
||||
<string name="fetching_profile">Fetching your profile...</string>
|
||||
<string name="saving_profile">Saving your profile</string>
|
||||
<string name="profile_saved">Changes saved!</string>
|
||||
<string name="error_profile">Something went wrong. Tap to retry</string>
|
||||
<string name="change_profile_picture">Change your profile picture</string>
|
||||
</resources>
|
|
@ -8331,6 +8331,14 @@
|
|||
<sha256 value="cced369639daee814671c2ff1b9d0f8acfbf598ce905487cb86feba54c9e8df1" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-android-extensions-runtime" version="1.7.20">
|
||||
<artifact name="kotlin-android-extensions-runtime-1.7.20.jar">
|
||||
<sha256 value="dc236f881dbe6c91e720f8b54df54427a0daf9a95ae6cf9a5c649a644afb234f" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="kotlin-android-extensions-runtime-1.7.20.pom">
|
||||
<sha256 value="3b076f3ad30172479605d09688544690aea4e997362fee6f2d6b1a765214917f" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-annotation-processing-gradle" version="1.6.10">
|
||||
<artifact name="kotlin-annotation-processing-gradle-1.6.10.jar">
|
||||
<sha256 value="a0fb6d2cc2ebee6258384d1501533dfeab2e41385ac0668dc2cac2e4dd5a377c" origin="Generated by Gradle"/>
|
||||
|
@ -8756,6 +8764,22 @@
|
|||
<sha256 value="470d4d0badde58da624747a9ea16432bffa8ac8de81effea7a1408ec74902705" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-parcelize-compiler" version="1.7.20">
|
||||
<artifact name="kotlin-parcelize-compiler-1.7.20.jar">
|
||||
<sha256 value="d13f98e935257b8608b93db874b4b2c7c5a0e7fe5d785f2061c1830ad17b3f0d" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="kotlin-parcelize-compiler-1.7.20.pom">
|
||||
<sha256 value="4a941647ba7fc045fbadf94642bcedf25f10f669130386efd8b66e23bbd89e60" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-parcelize-runtime" version="1.7.20">
|
||||
<artifact name="kotlin-parcelize-runtime-1.7.20.jar">
|
||||
<sha256 value="da8d81fc588612d4b7282c35a209dbb3b974a3bc0fa5330a5db57b9583570ebd" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
<artifact name="kotlin-parcelize-runtime-1.7.20.pom">
|
||||
<sha256 value="2c4ea1b17d2b63dde60e32191a512adb3c7c35da033502b8dbc0ba7ae0cf140c" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.jetbrains.kotlin" name="kotlin-project-model" version="1.6.10">
|
||||
<artifact name="kotlin-project-model-1.6.10.jar">
|
||||
<sha256 value="8d9f5e8e5402a05fb8c20447a56b697dc0cf73ea5c55534a9ee6a463920869c9" origin="Generated by Gradle"/>
|
||||
|
|
Loading…
Reference in New Issue