Merge branch 'edit_profile_picture' into 'master'
Fix profile pic edit Closes #377 See merge request pixeldroid/PixelDroid!569
This commit is contained in:
commit
370aeda4a6
@ -27,7 +27,7 @@ android {
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 23
|
||||
versionCode 26
|
||||
versionCode 27
|
||||
targetSdkVersion 34
|
||||
versionName "1.0.beta" + versionCode
|
||||
|
||||
|
@ -14,6 +14,7 @@ import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -29,6 +30,7 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
@ -36,7 +38,12 @@ import com.mikepenz.materialdrawer.iconics.iconicsIcon
|
||||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.interfaces.*
|
||||
import com.mikepenz.materialdrawer.model.interfaces.IProfile
|
||||
import com.mikepenz.materialdrawer.model.interfaces.descriptionRes
|
||||
import com.mikepenz.materialdrawer.model.interfaces.descriptionText
|
||||
import com.mikepenz.materialdrawer.model.interfaces.iconUrl
|
||||
import com.mikepenz.materialdrawer.model.interfaces.nameRes
|
||||
import com.mikepenz.materialdrawer.model.interfaces.nameText
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.widget.AccountHeaderView
|
||||
@ -53,10 +60,10 @@ import org.pixeldroid.app.searchDiscover.SearchDiscoverFragment
|
||||
import org.pixeldroid.app.settings.SettingsActivity
|
||||
import org.pixeldroid.app.utils.BaseActivity
|
||||
import org.pixeldroid.app.utils.api.objects.Notification
|
||||
import org.pixeldroid.app.utils.db.addUser
|
||||
import org.pixeldroid.app.utils.db.entities.HomeStatusDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.entities.PublicFeedStatusDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
||||
import org.pixeldroid.app.utils.hasInternet
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.INSTANCE_NOTIFICATION_TAG
|
||||
import org.pixeldroid.app.utils.notificationsWorker.NotificationsWorker.Companion.SHOW_NOTIFICATION_TAG
|
||||
@ -71,6 +78,8 @@ class MainActivity : BaseActivity() {
|
||||
private lateinit var header: AccountHeaderView
|
||||
private var user: UserDatabaseEntity? = null
|
||||
|
||||
private lateinit var model: MainActivityViewModel
|
||||
|
||||
companion object {
|
||||
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
|
||||
}
|
||||
@ -102,6 +111,12 @@ class MainActivity : BaseActivity() {
|
||||
} else {
|
||||
sendTraceDroidStackTracesIfExist("contact@pixeldroid.org", this)
|
||||
|
||||
val _model: MainActivityViewModel by viewModels {
|
||||
MainActivityViewModelFactory(application)
|
||||
}
|
||||
model = _model
|
||||
|
||||
|
||||
setupDrawer()
|
||||
val tabs: List<() -> Fragment> = listOf(
|
||||
{
|
||||
@ -196,6 +211,7 @@ class MainActivity : BaseActivity() {
|
||||
Glide.with(this@MainActivity)
|
||||
.load(uri)
|
||||
.placeholder(placeholder)
|
||||
.circleCrop()
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
@ -281,16 +297,12 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
val domain = user?.instance_uri.orEmpty()
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
val refreshToken = user?.refreshToken
|
||||
val clientId = user?.clientId.orEmpty()
|
||||
val clientSecret = user?.clientSecret.orEmpty()
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
|
||||
val account = api.verifyCredentials()
|
||||
addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
|
||||
fillDrawerAccountInfo(account.id!!)
|
||||
updateUserInfoDb(db, account)
|
||||
|
||||
//No need to update drawer account info here, the ViewModel listens to db updates
|
||||
} catch (exception: Exception) {
|
||||
Log.e("ACCOUNT UPDATE:", exception.toString())
|
||||
}
|
||||
@ -337,35 +349,41 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun fillDrawerAccountInfo(account: String) {
|
||||
val users = db.userDao().getAll().toMutableList()
|
||||
users.sortWith { l, r ->
|
||||
when {
|
||||
l.isActive && !r.isActive -> -1
|
||||
r.isActive && !l.isActive -> 1
|
||||
else -> 0
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
model.users.collect { list ->
|
||||
val users = list.toMutableList()
|
||||
users.sortWith { l, r ->
|
||||
when {
|
||||
l.isActive && !r.isActive -> -1
|
||||
r.isActive && !l.isActive -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
val profiles: MutableList<IProfile> = users.map { user ->
|
||||
ProfileDrawerItem().apply {
|
||||
isSelected = user.isActive
|
||||
nameText = user.display_name
|
||||
iconUrl = user.avatar_static
|
||||
isNameShown = true
|
||||
identifier = user.user_id.toLong()
|
||||
descriptionText = user.fullHandle
|
||||
tag = user.instance_uri
|
||||
}
|
||||
}.toMutableList()
|
||||
|
||||
// reuse the already existing "add account" item
|
||||
header.profiles.orEmpty()
|
||||
.filter { it.identifier == ADD_ACCOUNT_IDENTIFIER }
|
||||
.take(1)
|
||||
.forEach { profiles.add(it) }
|
||||
|
||||
header.clear()
|
||||
header.profiles = profiles
|
||||
header.setActiveProfile(account.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
val profiles: MutableList<IProfile> = users.map { user ->
|
||||
ProfileDrawerItem().apply {
|
||||
isSelected = user.isActive
|
||||
nameText = user.display_name
|
||||
iconUrl = user.avatar_static
|
||||
isNameShown = true
|
||||
identifier = user.user_id.toLong()
|
||||
descriptionText = user.fullHandle
|
||||
tag = user.instance_uri
|
||||
}
|
||||
}.toMutableList()
|
||||
|
||||
// reuse the already existing "add account" item
|
||||
header.profiles.orEmpty()
|
||||
.filter { it.identifier == ADD_ACCOUNT_IDENTIFIER }
|
||||
.take(1)
|
||||
.forEach { profiles.add(it) }
|
||||
|
||||
header.clear()
|
||||
header.profiles = profiles
|
||||
header.setActiveProfile(account.toLong())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,52 @@
|
||||
package org.pixeldroid.app
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
@Inject
|
||||
lateinit var db: AppDatabase
|
||||
|
||||
// Mutable state flow that will be used internally in the ViewModel, empty list is given as initial value.
|
||||
private val _users = MutableStateFlow(emptyList<UserDatabaseEntity>())
|
||||
|
||||
// Immutable state flow exposed to UI
|
||||
val users = _users.asStateFlow()
|
||||
|
||||
|
||||
init {
|
||||
(application as PixelDroidApplication).getAppComponent().inject(this)
|
||||
getUsers()
|
||||
}
|
||||
|
||||
private fun getUsers() {
|
||||
viewModelScope.launch {
|
||||
db.userDao().getAllFlow().flowOn(Dispatchers.IO)
|
||||
.collect { users: List<UserDatabaseEntity> ->
|
||||
_users.update { users }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MainActivityViewModelFactory(
|
||||
val application: Application,
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.getConstructor(Application::class.java).newInstance(application)
|
||||
}
|
||||
}
|
@ -73,6 +73,7 @@ internal fun <T: Any> initAdapter(
|
||||
|
||||
swipeRefreshLayout.setOnRefreshListener {
|
||||
adapter.refresh()
|
||||
adapter.notifyDataSetChanged()
|
||||
header?.refreshStories()
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,7 @@ class EditProfileActivity : BaseActivity() {
|
||||
}.show()
|
||||
} else {
|
||||
this.isEnabled = false
|
||||
if (model.submittedChanges) setResult(RESULT_OK)
|
||||
super.onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
@ -58,23 +59,24 @@ class EditProfileActivity : BaseActivity() {
|
||||
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(binding.bioEditText.text.toString() != uiState.bio) binding.bioEditText.setText(uiState.bio)
|
||||
if(binding.nameEditText.text.toString() != uiState.name) binding.nameEditText.setText(uiState.name)
|
||||
|
||||
binding.progressCard.visibility = if(uiState.loadingProfile || uiState.sendingProfile || uiState.uploadingPicture || 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
|
||||
binding.savingProgressBar.visibility =
|
||||
if(uiState.error || (uiState.profileSent && !uiState.uploadingPicture)) View.GONE
|
||||
else View.VISIBLE
|
||||
|
||||
if(uiState.profileSent){
|
||||
if(uiState.profileSent && !uiState.uploadingPicture && !uiState.error){
|
||||
binding.progressText.setText(R.string.profile_saved)
|
||||
binding.done.visibility = View.VISIBLE
|
||||
} else {
|
||||
@ -112,18 +114,18 @@ class EditProfileActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
binding.profilePic.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 ->
|
||||
@ -137,10 +139,10 @@ class EditProfileActivity : BaseActivity() {
|
||||
val imageUri: String = clipData.getItemAt(i).uri.toString()
|
||||
images.add(imageUri)
|
||||
}
|
||||
model.uploadImage(images.first())
|
||||
model.updateImage(images.first())
|
||||
} else if (data.data != null) {
|
||||
images.add(data.data!!.toString())
|
||||
model.uploadImage(images.first())
|
||||
model.updateImage(images.first())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,10 @@ import org.pixeldroid.app.postCreation.ProgressRequestBody
|
||||
import org.pixeldroid.app.posts.fromHtml
|
||||
import org.pixeldroid.app.utils.PixelDroidApplication
|
||||
import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
||||
import org.pixeldroid.app.utils.di.PixelfedAPIHolder
|
||||
import retrofit2.HttpException
|
||||
import javax.inject.Inject
|
||||
|
||||
class EditProfileViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@ -31,10 +34,16 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
@Inject
|
||||
lateinit var apiHolder: PixelfedAPIHolder
|
||||
|
||||
@Inject
|
||||
lateinit var db: AppDatabase
|
||||
|
||||
private val _uiState = MutableStateFlow(EditProfileActivityUiState())
|
||||
val uiState: StateFlow<EditProfileActivityUiState> = _uiState
|
||||
|
||||
var oldProfile: Account? = null
|
||||
private var oldProfile: Account? = null
|
||||
|
||||
var submittedChanges = false
|
||||
private set
|
||||
|
||||
init {
|
||||
(application as PixelDroidApplication).getAppComponent().inject(this)
|
||||
@ -46,6 +55,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
try {
|
||||
val profile = api.verifyCredentials()
|
||||
updateUserInfoDb(db, profile)
|
||||
if (oldProfile == null) oldProfile = profile
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
@ -76,15 +86,10 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
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
|
||||
)
|
||||
}
|
||||
@ -97,12 +102,17 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
note = bio,
|
||||
locked = privateAccount,
|
||||
)
|
||||
if (madeChanges()) submittedChanges = true
|
||||
oldProfile = account
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
bio = account.source?.note ?: account.note?.let {fromHtml(it).toString()},
|
||||
bio = account.source?.note
|
||||
?: account.note?.let { fromHtml(it).toString() },
|
||||
name = account.display_name,
|
||||
profilePictureUri = account.anyAvatar()?.toUri(),
|
||||
profilePictureUri = if (profilePictureChanged) profilePictureUri
|
||||
else account.anyAvatar()?.toUri(),
|
||||
uploadProgress = 0,
|
||||
uploadingPicture = profilePictureChanged,
|
||||
privateAccount = account.locked,
|
||||
sendingProfile = false,
|
||||
profileSent = true,
|
||||
@ -111,14 +121,13 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
error = false
|
||||
)
|
||||
}
|
||||
if(profilePictureChanged) uploadImage()
|
||||
} catch (exception: Exception) {
|
||||
Log.e("TAG", exception.toString())
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
sendingProfile = false,
|
||||
profileSent = false,
|
||||
loadingProfile = false,
|
||||
profileLoaded = false,
|
||||
error = true
|
||||
)
|
||||
}
|
||||
@ -145,20 +154,16 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
}
|
||||
}
|
||||
|
||||
fun changesApplied() {
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(profileLoaded = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun madeChanges(): Boolean =
|
||||
with(uiState.value) {
|
||||
val bioUnchanged: Boolean = oldProfile?.source?.note?.let { it != bio }
|
||||
// If source note is null, check note
|
||||
val privateChanged = oldProfile?.locked != privateAccount
|
||||
val displayNameChanged = oldProfile?.display_name != name
|
||||
val bioChanged: Boolean = oldProfile?.source?.note?.let { it != bio }
|
||||
// If source note is null, check note
|
||||
?: oldProfile?.note?.let { fromHtml(it).toString() != bio }
|
||||
?: true
|
||||
oldProfile?.locked != privateAccount || oldProfile?.display_name != name
|
||||
|| bioUnchanged
|
||||
|
||||
profilePictureChanged || privateChanged || displayNameChanged || bioChanged
|
||||
}
|
||||
|
||||
fun clickedCard() {
|
||||
@ -178,16 +183,27 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadImage(image: String) {
|
||||
//TODO fix
|
||||
fun updateImage(image: String) {
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
profilePictureUri = image.toUri(),
|
||||
profilePictureChanged = true,
|
||||
profileSent = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun uploadImage() {
|
||||
val image = uiState.value.profilePictureUri!!
|
||||
|
||||
val inputStream =
|
||||
getApplication<PixelDroidApplication>().contentResolver.openInputStream(image.toUri())
|
||||
getApplication<PixelDroidApplication>().contentResolver.openInputStream(image)
|
||||
?: return
|
||||
|
||||
val size: Long =
|
||||
if (image.toUri().scheme == "content") {
|
||||
if (image.scheme == "content") {
|
||||
getApplication<PixelDroidApplication>().contentResolver.query(
|
||||
image.toUri(),
|
||||
image,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
@ -203,7 +219,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
cursor.getLong(sizeIndex)
|
||||
} ?: 0
|
||||
} else {
|
||||
image.toUri().toFile().length()
|
||||
image.toFile().length()
|
||||
}
|
||||
|
||||
val imagePart = ProgressRequestBody(inputStream, size, "image/*")
|
||||
@ -225,21 +241,32 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
var postSub: Disposable? = null
|
||||
|
||||
val api = apiHolder.api ?: apiHolder.setToCurrentUser()
|
||||
val inter = api.updateProfilePicture(requestBody.parts[0])
|
||||
|
||||
val pixelfed = db.instanceDao().getActiveInstance().pixelfed
|
||||
|
||||
val inter =
|
||||
if(pixelfed) api.updateProfilePicture(requestBody.parts[0])
|
||||
else api.updateProfilePictureMastodon(requestBody.parts[0])
|
||||
|
||||
postSub = inter
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ it: Account ->
|
||||
Log.e("qsdfqsdfs", it.toString())
|
||||
|
||||
/* onNext = */ { account: Account ->
|
||||
account.anyAvatar()?.let {
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
profilePictureUri = it.toUri()
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ e: Throwable ->
|
||||
/* onError = */ { e: Throwable ->
|
||||
Log.e("error", (e as? HttpException)?.message().orEmpty())
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
uploadProgress = 0,
|
||||
uploadingPicture = true,
|
||||
uploadingPicture = false,
|
||||
error = true
|
||||
)
|
||||
}
|
||||
@ -247,9 +274,10 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
postSub?.dispose()
|
||||
sub.dispose()
|
||||
},
|
||||
{
|
||||
/* onComplete = */ {
|
||||
_uiState.update { currentUiState ->
|
||||
currentUiState.copy(
|
||||
profilePictureChanged = false,
|
||||
uploadProgress = 100,
|
||||
uploadingPicture = false
|
||||
)
|
||||
@ -265,7 +293,8 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
|
||||
data class EditProfileActivityUiState(
|
||||
val name: String? = null,
|
||||
val bio: String? = null,
|
||||
val profilePictureUri: Uri?= null,
|
||||
val profilePictureUri: Uri? = null,
|
||||
val profilePictureChanged: Boolean = false,
|
||||
val privateAccount: Boolean? = null,
|
||||
val loadingProfile: Boolean = true,
|
||||
val profileLoaded: Boolean = false,
|
||||
|
@ -6,6 +6,7 @@ import android.text.method.LinkMovementMethod
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@ -16,11 +17,14 @@ import com.google.android.material.tabs.TabLayoutMediator
|
||||
import kotlinx.coroutines.launch
|
||||
import org.pixeldroid.app.R
|
||||
import org.pixeldroid.app.databinding.ActivityProfileBinding
|
||||
import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedFeedFragment
|
||||
import org.pixeldroid.app.posts.parseHTMLText
|
||||
import org.pixeldroid.app.utils.BaseActivity
|
||||
import org.pixeldroid.app.utils.api.PixelfedAPI
|
||||
import org.pixeldroid.app.utils.api.objects.Account
|
||||
import org.pixeldroid.app.utils.api.objects.FeedContent
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
import org.pixeldroid.app.utils.db.updateUserInfoDb
|
||||
import org.pixeldroid.app.utils.setProfileImageFromURL
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
@ -56,7 +60,7 @@ class ProfileActivity : BaseActivity() {
|
||||
setContent(account)
|
||||
}
|
||||
|
||||
private fun createProfileTabs(account: Account?): Array<Fragment>{
|
||||
private fun createProfileTabs(account: Account?): Array<UncachedFeedFragment<FeedContent>> {
|
||||
|
||||
val profileFeedFragment = ProfileFeedFragment()
|
||||
profileFeedFragment.arguments = Bundle().apply {
|
||||
@ -80,7 +84,7 @@ class ProfileActivity : BaseActivity() {
|
||||
putSerializable(ProfileFeedFragment.COLLECTIONS, true)
|
||||
}
|
||||
|
||||
val returnArray: Array<Fragment> = arrayOf(
|
||||
val returnArray: Array<UncachedFeedFragment<FeedContent>> = arrayOf(
|
||||
profileGridFragment,
|
||||
profileFeedFragment,
|
||||
profileCollectionsFragment
|
||||
@ -100,7 +104,7 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun setupTabs(
|
||||
tabs: Array<Fragment>
|
||||
tabs: Array<UncachedFeedFragment<FeedContent>>
|
||||
){
|
||||
binding.viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
@ -134,7 +138,6 @@ class ProfileActivity : BaseActivity() {
|
||||
}.attach()
|
||||
}
|
||||
|
||||
|
||||
private fun setContent(account: Account?) {
|
||||
if(account != null) {
|
||||
setViews(account)
|
||||
@ -152,6 +155,9 @@ class ProfileActivity : BaseActivity() {
|
||||
).show()
|
||||
return@launchWhenResumed
|
||||
}
|
||||
|
||||
updateUserInfoDb(db, myAccount)
|
||||
|
||||
setViews(myAccount)
|
||||
}
|
||||
}
|
||||
@ -217,9 +223,15 @@ class ProfileActivity : BaseActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
private val editResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
// Profile was edited, reload
|
||||
setContent(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onClickEditButton() {
|
||||
val intent = Intent(this, EditProfileActivity::class.java)
|
||||
ContextCompat.startActivity(this, intent, null)
|
||||
editResult.launch(Intent(this, EditProfileActivity::class.java))
|
||||
}
|
||||
|
||||
private fun onClickFollowers(account: Account?) {
|
||||
|
@ -338,18 +338,31 @@ 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
|
||||
|
||||
/**
|
||||
* Pixelfed uses PHP, multipart uploads don't work through PATCH so we use POST as suggested
|
||||
* here: https://github.com/pixelfed/pixelfed/issues/4250
|
||||
* However, changing to POST breaks the upload on Mastodon.
|
||||
*
|
||||
* To have this work on Pixelfed and Mastodon without special logic to distinguish the two,
|
||||
* we'll have to wait for PHP 8.4 and https://wiki.php.net/rfc/rfc1867-non-post
|
||||
* which should come out end of 2024
|
||||
*/
|
||||
@Multipart
|
||||
@POST("/api/v1/accounts/update_credentials")
|
||||
fun updateProfilePicture(
|
||||
@Part avatar: MultipartBody.Part?
|
||||
): Observable<Account>
|
||||
|
||||
@Multipart
|
||||
@PATCH("/api/v1/accounts/update_credentials")
|
||||
fun updateProfilePicture(
|
||||
fun updateProfilePictureMastodon(
|
||||
@Part avatar: MultipartBody.Part?
|
||||
): Observable<Account>
|
||||
|
||||
|
@ -22,7 +22,7 @@ import org.pixeldroid.app.utils.api.objects.Notification
|
||||
PublicFeedStatusDatabaseEntity::class,
|
||||
Notification::class
|
||||
],
|
||||
version = 5
|
||||
version = 6
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@ -44,4 +44,9 @@ val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE instances ADD COLUMN videoEnabled INTEGER NOT NULL DEFAULT 1")
|
||||
}
|
||||
}
|
||||
val MIGRATION_5_6 = object : Migration(5, 6) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE instances ADD COLUMN pixelfed INTEGER NOT NULL DEFAULT 1")
|
||||
}
|
||||
}
|
@ -13,21 +13,34 @@ import org.pixeldroid.app.utils.db.entities.InstanceDatabaseEntity.Companion.DEF
|
||||
import org.pixeldroid.app.utils.normalizeDomain
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String) {
|
||||
suspend fun addUser(
|
||||
db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true,
|
||||
accessToken: String, refreshToken: String?, clientId: String, clientSecret: String,
|
||||
) {
|
||||
db.userDao().insertOrUpdate(
|
||||
UserDatabaseEntity(
|
||||
user_id = account.id!!,
|
||||
instance_uri = normalizeDomain(instance_uri),
|
||||
username = account.username!!,
|
||||
display_name = account.getDisplayName(),
|
||||
avatar_static = account.anyAvatar().orEmpty(),
|
||||
isActive = activeUser,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
UserDatabaseEntity(
|
||||
user_id = account.id!!,
|
||||
instance_uri = normalizeDomain(instance_uri),
|
||||
username = account.username!!,
|
||||
display_name = account.getDisplayName(),
|
||||
avatar_static = account.anyAvatar().orEmpty(),
|
||||
isActive = activeUser,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun updateUserInfoDb(db: AppDatabase, account: Account) {
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
db.userDao().updateUserAccountDetails(
|
||||
account.username.orEmpty(),
|
||||
account.display_name.orEmpty(),
|
||||
account.anyAvatar().orEmpty(),
|
||||
user.user_id,
|
||||
user.instance_uri
|
||||
)
|
||||
}
|
||||
|
||||
@ -37,17 +50,21 @@ fun storeInstance(db: AppDatabase, nodeInfo: NodeInfo?, instance: Instance? = nu
|
||||
uri = normalizeDomain(metadata?.config?.site?.url!!),
|
||||
title = metadata.config.site.name!!,
|
||||
maxStatusChars = metadata.config.uploader?.max_caption_length!!.toInt(),
|
||||
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_PHOTO_SIZE,
|
||||
maxPhotoSize = metadata.config.uploader.max_photo_size?.toIntOrNull()
|
||||
?: DEFAULT_MAX_PHOTO_SIZE,
|
||||
// Pixelfed doesn't distinguish between max photo and video size
|
||||
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull() ?: DEFAULT_MAX_VIDEO_SIZE,
|
||||
maxVideoSize = metadata.config.uploader.max_photo_size?.toIntOrNull()
|
||||
?: DEFAULT_MAX_VIDEO_SIZE,
|
||||
albumLimit = metadata.config.uploader.album_limit?.toIntOrNull() ?: DEFAULT_ALBUM_LIMIT,
|
||||
videoEnabled = metadata.config.features?.video ?: DEFAULT_VIDEO_ENABLED
|
||||
videoEnabled = metadata.config.features?.video ?: DEFAULT_VIDEO_ENABLED,
|
||||
pixelfed = metadata.software?.repo?.contains("pixelfed", ignoreCase = true) == true
|
||||
)
|
||||
} ?: instance?.run {
|
||||
InstanceDatabaseEntity(
|
||||
uri = normalizeDomain(uri.orEmpty()),
|
||||
title = title.orEmpty(),
|
||||
maxStatusChars = max_toot_chars?.toInt() ?: DEFAULT_MAX_TOOT_CHARS,
|
||||
uri = normalizeDomain(uri.orEmpty()),
|
||||
title = title.orEmpty(),
|
||||
maxStatusChars = max_toot_chars?.toInt() ?: DEFAULT_MAX_TOOT_CHARS,
|
||||
pixelfed = false
|
||||
)
|
||||
} ?: throw IllegalArgumentException("Cannot store instance where both are null")
|
||||
|
||||
|
@ -11,6 +11,10 @@ interface InstanceDao {
|
||||
@Query("SELECT * FROM instances WHERE uri=:instanceUri")
|
||||
fun getInstance(instanceUri: String): InstanceDatabaseEntity
|
||||
|
||||
|
||||
@Query("SELECT * FROM instances WHERE uri=(SELECT users.instance_uri FROM users WHERE isActive=1)")
|
||||
fun getActiveInstance(): InstanceDatabaseEntity
|
||||
|
||||
/**
|
||||
* Insert an instance, if it already exists return -1
|
||||
*/
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.pixeldroid.app.utils.db.dao
|
||||
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.pixeldroid.app.utils.db.entities.UserDatabaseEntity
|
||||
|
||||
@Dao
|
||||
@ -9,17 +10,21 @@ interface UserDao {
|
||||
* Insert a user, if it already exists return -1
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insertUser(user: UserDatabaseEntity): Long
|
||||
suspend fun insertUser(user: UserDatabaseEntity): Long
|
||||
|
||||
@Transaction
|
||||
fun insertOrUpdate(user: UserDatabaseEntity) {
|
||||
suspend fun insertOrUpdate(user: UserDatabaseEntity) {
|
||||
if (insertUser(user) == -1L) {
|
||||
updateUser(user)
|
||||
}
|
||||
}
|
||||
|
||||
@Update
|
||||
fun updateUser(user: UserDatabaseEntity)
|
||||
suspend fun updateUser(user: UserDatabaseEntity)
|
||||
|
||||
@Query("UPDATE users SET username = :username, display_name = :displayName, avatar_static = :avatarStatic WHERE user_id = :id and instance_uri = :instanceUri")
|
||||
suspend fun updateUserAccountDetails(username: String, displayName: String, avatarStatic: String, id: String, instanceUri: String)
|
||||
|
||||
|
||||
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instanceUri")
|
||||
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instanceUri: String)
|
||||
@ -27,6 +32,9 @@ interface UserDao {
|
||||
@Query("SELECT * FROM users")
|
||||
fun getAll(): List<UserDatabaseEntity>
|
||||
|
||||
@Query("SELECT * FROM users")
|
||||
fun getAllFlow(): Flow<List<UserDatabaseEntity>>
|
||||
|
||||
@Query("SELECT * FROM users WHERE isActive=1")
|
||||
fun getActiveUser(): UserDatabaseEntity?
|
||||
|
||||
|
@ -4,20 +4,22 @@ import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "instances")
|
||||
data class InstanceDatabaseEntity (
|
||||
@PrimaryKey var uri: String,
|
||||
var title: String,
|
||||
var maxStatusChars: Int = DEFAULT_MAX_TOOT_CHARS,
|
||||
// Per-file file-size limit in KB. Defaults to 15000 (15MB). Default limit for Mastodon is 8MB
|
||||
var maxPhotoSize: Int = DEFAULT_MAX_PHOTO_SIZE,
|
||||
// Mastodon has different file limits for videos, default of 40MB
|
||||
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
||||
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
||||
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
||||
// Is video functionality enabled on this instance?
|
||||
var videoEnabled: Boolean = DEFAULT_VIDEO_ENABLED,
|
||||
data class InstanceDatabaseEntity(
|
||||
@PrimaryKey var uri: String,
|
||||
var title: String,
|
||||
var maxStatusChars: Int = DEFAULT_MAX_TOOT_CHARS,
|
||||
// Per-file file-size limit in KB. Defaults to 15000 (15MB). Default limit for Mastodon is 8MB
|
||||
var maxPhotoSize: Int = DEFAULT_MAX_PHOTO_SIZE,
|
||||
// Mastodon has different file limits for videos, default of 40MB
|
||||
var maxVideoSize: Int = DEFAULT_MAX_VIDEO_SIZE,
|
||||
// How many photos can go into an album. Default limit for Pixelfed and Mastodon is 4
|
||||
var albumLimit: Int = DEFAULT_ALBUM_LIMIT,
|
||||
// Is video functionality enabled on this instance?
|
||||
var videoEnabled: Boolean = DEFAULT_VIDEO_ENABLED,
|
||||
// Is this Pixelfed instance?
|
||||
var pixelfed: Boolean = true,
|
||||
) {
|
||||
companion object{
|
||||
companion object {
|
||||
// Default max number of chars for Mastodon: used when their is no other value supplied by
|
||||
// either NodeInfo or the instance endpoint
|
||||
const val DEFAULT_MAX_TOOT_CHARS = 500
|
||||
|
@ -7,6 +7,7 @@ import org.pixeldroid.app.utils.PixelDroidApplication
|
||||
import org.pixeldroid.app.utils.db.AppDatabase
|
||||
import org.pixeldroid.app.utils.BaseFragment
|
||||
import dagger.Component
|
||||
import org.pixeldroid.app.MainActivityViewModel
|
||||
import org.pixeldroid.app.postCreation.PostCreationViewModel
|
||||
import org.pixeldroid.app.profile.EditProfileViewModel
|
||||
import org.pixeldroid.app.stories.StoriesViewModel
|
||||
@ -25,6 +26,7 @@ interface ApplicationComponent {
|
||||
fun inject(postCreationViewModel: PostCreationViewModel)
|
||||
fun inject(editProfileViewModel: EditProfileViewModel)
|
||||
fun inject(storiesViewModel: StoriesViewModel)
|
||||
fun inject(mainActivityViewModel: MainActivityViewModel)
|
||||
|
||||
val context: Context?
|
||||
val application: Application?
|
||||
|
@ -7,6 +7,7 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import org.pixeldroid.app.utils.db.MIGRATION_3_4
|
||||
import org.pixeldroid.app.utils.db.MIGRATION_4_5
|
||||
import org.pixeldroid.app.utils.db.MIGRATION_5_6
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@ -18,7 +19,7 @@ class DatabaseModule(private val context: Context) {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java, "pixeldroid"
|
||||
).addMigrations(MIGRATION_3_4).addMigrations(MIGRATION_4_5)
|
||||
).addMigrations(MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
|
||||
.allowMainThreadQueries().build()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user