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: 'kotlin-kapt'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
|
|
||||||
// Force latest version of Jacoco, initially done to resolve https://github.com/jacoco/jacoco/issues/1155
|
// Force latest version of Jacoco, initially done to resolve https://github.com/jacoco/jacoco/issues/1155
|
||||||
jacoco.toolVersion = "0.8.7"
|
jacoco.toolVersion = "0.8.7"
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,9 @@
|
||||||
android:name=".posts.AlbumActivity"
|
android:name=".posts.AlbumActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/AppTheme.ActionBar.Transparent"/>
|
android:theme="@style/AppTheme.ActionBar.Transparent"/>
|
||||||
|
<activity
|
||||||
|
android:name=".profile.EditProfileActivity"
|
||||||
|
android:exported="false"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".posts.MediaViewerActivity"
|
android:name=".posts.MediaViewerActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
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() {
|
private fun onClickEditButton() {
|
||||||
val url = "$domain/settings/home"
|
val intent = Intent(this, EditProfileActivity::class.java)
|
||||||
|
ContextCompat.startActivity(this, intent, null)
|
||||||
if(!openUrl(url)) {
|
|
||||||
Snackbar.make(binding.root, getString(R.string.edit_link_failed),
|
|
||||||
Snackbar.LENGTH_LONG).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onClickFollowers(account: Account?) {
|
private fun onClickFollowers(account: Account?) {
|
||||||
|
|
|
@ -295,6 +295,20 @@ interface PixelfedAPI {
|
||||||
@Header("Authorization") authorization: String? = null
|
@Header("Authorization") authorization: String? = null
|
||||||
): Account
|
): 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")
|
@GET("/api/v1/accounts/{id}/statuses")
|
||||||
suspend fun accountPosts(
|
suspend fun accountPosts(
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
package org.pixeldroid.app.utils.api.objects
|
package org.pixeldroid.app.utils.api.objects
|
||||||
|
|
||||||
import java.io.Serializable
|
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
|
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 org.pixeldroid.app.utils.BaseFragment
|
||||||
import dagger.Component
|
import dagger.Component
|
||||||
import org.pixeldroid.app.postCreation.PostCreationViewModel
|
import org.pixeldroid.app.postCreation.PostCreationViewModel
|
||||||
|
import org.pixeldroid.app.profile.EditProfileViewModel
|
||||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
|
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ interface ApplicationComponent {
|
||||||
fun inject(feedFragment: BaseFragment)
|
fun inject(feedFragment: BaseFragment)
|
||||||
fun inject(notificationsWorker: NotificationsWorker)
|
fun inject(notificationsWorker: NotificationsWorker)
|
||||||
fun inject(postCreationViewModel: PostCreationViewModel)
|
fun inject(postCreationViewModel: PostCreationViewModel)
|
||||||
|
fun inject(editProfileViewModel: EditProfileViewModel)
|
||||||
|
|
||||||
val context: Context?
|
val context: Context?
|
||||||
val application: Application?
|
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:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:hint="@string/domain_of_your_instance"
|
android:hint="@string/domain_of_your_instance"
|
||||||
|
android:visibility="gone"
|
||||||
app:errorEnabled="true"
|
app:errorEnabled="true"
|
||||||
android:visibility="gone">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/editText"
|
android:id="@+id/editText"
|
||||||
|
|
|
@ -116,7 +116,6 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/edit_profile"
|
android:text="@string/edit_profile"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:icon="@drawable/ic_baseline_open_in_browser_24"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
app:layout_constraintBottom_toBottomOf="@+id/profilePictureImageView"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
app:layout_constraintStart_toEndOf="@+id/profilePictureImageView"
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:lineSpacingExtra="8sp"
|
android:lineSpacingExtra="8sp"
|
||||||
android:text="Use dynamic color from your system"
|
android:text="@string/use_dynamic_color"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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="one">%d reply</item>
|
||||||
<item quantity="other">%d replies</item>
|
<item quantity="other">%d replies</item>
|
||||||
</plurals>
|
</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>
|
</resources>
|
|
@ -8331,6 +8331,14 @@
|
||||||
<sha256 value="cced369639daee814671c2ff1b9d0f8acfbf598ce905487cb86feba54c9e8df1" origin="Generated by Gradle"/>
|
<sha256 value="cced369639daee814671c2ff1b9d0f8acfbf598ce905487cb86feba54c9e8df1" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="org.jetbrains.kotlin" name="kotlin-annotation-processing-gradle" version="1.6.10">
|
||||||
<artifact name="kotlin-annotation-processing-gradle-1.6.10.jar">
|
<artifact name="kotlin-annotation-processing-gradle-1.6.10.jar">
|
||||||
<sha256 value="a0fb6d2cc2ebee6258384d1501533dfeab2e41385ac0668dc2cac2e4dd5a377c" origin="Generated by Gradle"/>
|
<sha256 value="a0fb6d2cc2ebee6258384d1501533dfeab2e41385ac0668dc2cac2e4dd5a377c" origin="Generated by Gradle"/>
|
||||||
|
@ -8756,6 +8764,22 @@
|
||||||
<sha256 value="470d4d0badde58da624747a9ea16432bffa8ac8de81effea7a1408ec74902705" origin="Generated by Gradle"/>
|
<sha256 value="470d4d0badde58da624747a9ea16432bffa8ac8de81effea7a1408ec74902705" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="org.jetbrains.kotlin" name="kotlin-project-model" version="1.6.10">
|
||||||
<artifact name="kotlin-project-model-1.6.10.jar">
|
<artifact name="kotlin-project-model-1.6.10.jar">
|
||||||
<sha256 value="8d9f5e8e5402a05fb8c20447a56b697dc0cf73ea5c55534a9ee6a463920869c9" origin="Generated by Gradle"/>
|
<sha256 value="8d9f5e8e5402a05fb8c20447a56b697dc0cf73ea5c55534a9ee6a463920869c9" origin="Generated by Gradle"/>
|
||||||
|
|
Loading…
Reference in New Issue