This commit is contained in:
Matthieu 2024-01-24 22:20:08 +01:00
parent 1dcf605976
commit 2e399884e9
9 changed files with 175 additions and 61 deletions

View File

@ -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
@ -36,7 +37,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 +59,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 +77,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 +110,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(
{
@ -281,16 +295,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 +347,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())
}
/**

View File

@ -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)
}
}

View File

@ -73,6 +73,7 @@ internal fun <T: Any> initAdapter(
swipeRefreshLayout.setOnRefreshListener {
adapter.refresh()
adapter.notifyDataSetChanged()
header?.refreshStories()
}

View File

@ -51,6 +51,7 @@ class EditProfileActivity : BaseActivity() {
}.show()
} else {
this.isEnabled = false
if (model.submittedChanges) setResult(RESULT_OK)
super.onBackPressedDispatcher.onBackPressed()
}
}

View File

@ -24,7 +24,9 @@ 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) {
@ -40,6 +42,9 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
private var oldProfile: Account? = null
var submittedChanges = false
private set
init {
(application as PixelDroidApplication).getAppComponent().inject(this)
loadProfile()
@ -50,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(
@ -96,6 +102,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
note = bio,
locked = privateAccount,
)
if (madeChanges()) submittedChanges = true
oldProfile = account
_uiState.update { currentUiState ->
currentUiState.copy(
@ -180,7 +187,8 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
_uiState.update { currentUiState ->
currentUiState.copy(
profilePictureUri = image.toUri(),
profilePictureChanged = true
profilePictureChanged = true,
profileSent = false
)
}
}
@ -238,7 +246,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
val inter =
if(pixelfed) api.updateProfilePicture(requestBody.parts[0])
else api.updateProfilePictureMastodon((requestBody.parts[0]))
else api.updateProfilePictureMastodon(requestBody.parts[0])
postSub = inter
.subscribeOn(Schedulers.io())
@ -254,6 +262,7 @@ class EditProfileViewModel(application: Application) : AndroidViewModel(applicat
}
},
/* onError = */ { e: Throwable ->
Log.e("error", (e as? HttpException)?.message().orEmpty())
_uiState.update { currentUiState ->
currentUiState.copy(
uploadProgress = 0,

View File

@ -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?) {

View File

@ -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
)
}

View File

@ -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?

View File

@ -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?