Merge branch 'token_consistency' into 'master'

Better token refresh, refactor auth token with interceptor

See merge request pixeldroid/PixelDroid!326
This commit is contained in:
Matthieu 2021-04-19 10:01:40 +00:00
commit bccf57f918
27 changed files with 180 additions and 233 deletions

View File

@ -185,16 +185,17 @@ class MainActivity : BaseActivity() {
}
private fun getUpdatedAccount() {
if (hasInternet(applicationContext)) {
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.setDomainToCurrentUser(db)
lifecycleScope.launchWhenCreated {
try {
val account = api.verifyCredentials("Bearer $accessToken")
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.setDomainToCurrentUser(db)
val account = api.verifyCredentials()
addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
fillDrawerAccountInfo(account.id!!)
} catch (exception: IOException) {

View File

@ -29,7 +29,6 @@ import com.h.pixeldroid.postCreation.carousel.CarouselItem
import com.h.pixeldroid.postCreation.carousel.ImageCarousel
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.utils.BaseActivity
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Attachment
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
@ -58,8 +57,6 @@ data class PhotoData(
class PostCreationActivity : BaseActivity() {
private lateinit var accessToken: String
private var user: UserDatabaseEntity? = null
private lateinit var instance: InstanceDatabaseEntity
@ -85,8 +82,6 @@ class PostCreationActivity : BaseActivity() {
// get image URIs
intent.clipData?.let { addPossibleImages(it) }
accessToken = user?.accessToken.orEmpty()
val carousel: ImageCarousel = binding.carousel
carousel.addData(photoData.map { CarouselItem(it.imageUri) })
carousel.layoutCarouselCallback = {
@ -338,7 +333,7 @@ class PostCreationActivity : BaseActivity() {
val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val inter = api.mediaUpload("Bearer $accessToken", description, requestBody.parts[0])
val inter = api.mediaUpload(description, requestBody.parts[0])
postSub = inter
.subscribeOn(Schedulers.io())
@ -383,7 +378,6 @@ class PostCreationActivity : BaseActivity() {
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
api.postStatus(
authorization = "Bearer $accessToken",
statusText = description,
media_ids = photoData.mapNotNull { it.uploadId }.toList()
)

View File

@ -12,13 +12,13 @@ import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.text.toSpanned
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope
import com.h.pixeldroid.R
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId
import com.h.pixeldroid.utils.api.objects.Mention
import kotlinx.coroutines.coroutineScope
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import java.net.URI
import java.net.URISyntaxException
import java.text.ParseException
@ -50,12 +50,12 @@ fun getDomain(urlString: String?): String {
}
fun parseHTMLText(
text : String,
mentions: List<Mention>?,
api : PixelfedAPI,
context: Context,
credential: String,
lifecycleScope: LifecycleCoroutineScope
text: String,
mentions: List<Mention>?,
apiHolder: PixelfedAPIHolder,
context: Context,
lifecycleScope: LifecycleCoroutineScope,
db: AppDatabase
) : Spanned {
//Convert text to spannable
val content = fromHtml(text)
@ -108,7 +108,8 @@ fun parseHTMLText(
Log.e("MENTION", "CLICKED")
//Retrieve the account for the given profile
lifecycleScope.launchWhenCreated {
openAccountFromId(accountId, api, context, credential)
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
openAccountFromId(accountId, api, context)
}
}
}

View File

@ -25,7 +25,6 @@ import java.io.IOException
class PostActivity : BaseActivity() {
lateinit var domain : String
private lateinit var accessToken : String
private lateinit var binding: ActivityPostBinding
@ -45,17 +44,15 @@ class PostActivity : BaseActivity() {
val user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty()
accessToken = user?.accessToken.orEmpty()
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName())
val holder = StatusViewHolder(binding.postFragmentSingle)
holder.bind(status, apiHolder.api!!, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
holder.bind(status, apiHolder, db, lifecycleScope, displayDimensionsInPx(), isActivity = true)
val credential = "Bearer $accessToken"
activateCommenter(credential)
activateCommenter()
if(viewComments || postComment){
//Scroll already down as much as possible (since comments are not loaded yet)
@ -68,10 +65,10 @@ class PostActivity : BaseActivity() {
}
// also retrieve comments if we're not posting the comment
if(!postComment) retrieveComments(apiHolder.api!!, credential)
if(!postComment) retrieveComments(apiHolder.api!!)
}
binding.postFragmentSingle.viewComments.setOnClickListener {
retrieveComments(apiHolder.api!!, credential)
retrieveComments(apiHolder.api!!)
}
}
@ -80,7 +77,7 @@ class PostActivity : BaseActivity() {
return true
}
private fun activateCommenter(credential: String) {
private fun activateCommenter() {
//Activate commenter
binding.submitComment.setOnClickListener {
val textIn = binding.editComment.text
@ -94,15 +91,14 @@ class PostActivity : BaseActivity() {
} else {
//Post the comment
lifecycleScope.launchWhenCreated {
apiHolder.api?.let { it1 -> postComment(it1, credential) }
apiHolder.api?.let { it1 -> postComment(it1) }
}
}
}
}
private fun addComment(context: Context, commentContainer: LinearLayout,
commentUsername: String, commentContent: String, mentions: List<Mention>,
credential: String) {
commentUsername: String, commentContent: String, mentions: List<Mention>) {
val itemBinding = CommentBinding.inflate(
@ -113,25 +109,29 @@ class PostActivity : BaseActivity() {
itemBinding.commentText.text = parseHTMLText(
commentContent,
mentions,
apiHolder.api!!,
apiHolder,
context,
credential,
lifecycleScope
lifecycleScope,
db
)
}
private fun retrieveComments(api: PixelfedAPI, credential: String) {
private fun retrieveComments(api: PixelfedAPI) {
lifecycleScope.launchWhenCreated {
status.id.let {
try {
val statuses = api.statusComments(it, credential).descendants
val statuses = api.statusComments(it).descendants
binding.commentContainer.removeAllViews()
//Create the new views for each comment
for (status in statuses) {
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!,
status.content!!, status.mentions.orEmpty(), credential
addComment(
binding.root.context,
binding.commentContainer,
status.account!!.username!!,
status.content!!,
status.mentions.orEmpty()
)
}
binding.commentContainer.visibility = View.VISIBLE
@ -149,19 +149,18 @@ class PostActivity : BaseActivity() {
private suspend fun postComment(
api: PixelfedAPI,
credential: String,
) {
val textIn = binding.editComment.text
val nonNullText = textIn.toString()
status.id.let {
try {
val response = api.postStatus(credential, nonNullText, it)
val response = api.postStatus(nonNullText, it)
binding.commentIn.visibility = View.GONE
//Add the comment to the comment section
addComment(
binding.root.context, binding.commentContainer, response.account!!.username!!,
response.content!!, response.mentions.orEmpty(), credential
response.content!!, response.mentions.orEmpty()
)
Toast.makeText(

View File

@ -24,10 +24,6 @@ class ReportActivity : BaseActivity() {
val status = intent.getSerializableExtra(Status.POST_TAG) as Status?
//get the currently active user
val user = db.userDao().getActiveUser()
binding.reportTargetTextview.text = getString(R.string.report_target).format(status?.account?.acct)
@ -37,12 +33,15 @@ class ReportActivity : BaseActivity() {
binding.textInputLayout.editText?.isEnabled = false
val accessToken = user?.accessToken.orEmpty()
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
lifecycleScope.launchWhenCreated {
try {
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), binding.textInputLayout.editText?.text.toString())
api.report(
status?.account?.id!!,
listOf(status),
binding.textInputLayout.editText?.text.toString()
)
reportStatus(true)
} catch (exception: IOException) {

View File

@ -29,6 +29,7 @@ import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_COMMENT_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.POST_TAG
import com.h.pixeldroid.utils.api.objects.Status.Companion.VIEW_COMMENTS_TAG
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.di.PixelfedAPIHolder
import com.karumi.dexter.Dexter
import com.karumi.dexter.listener.PermissionDeniedResponse
import com.karumi.dexter.listener.PermissionGrantedResponse
@ -46,7 +47,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
private var status: Status? = null
fun bind(status: Status?, pixelfedAPI: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, isActivity: Boolean = false) {
fun bind(status: Status?, pixelfedAPI: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope, displayDimensionsInPx: Pair<Int, Int>, isActivity: Boolean = false) {
this.itemView.visibility = View.VISIBLE
this.status = status
@ -177,9 +178,9 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
private fun setDescription(
api: PixelfedAPI,
credential: String,
lifecycleScope: LifecycleCoroutineScope
apiHolder: PixelfedAPIHolder,
lifecycleScope: LifecycleCoroutineScope,
db: AppDatabase
) {
binding.description.apply {
if (status?.content.isNullOrBlank()) {
@ -188,10 +189,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
text = parseHTMLText(
status?.content.orEmpty(),
status?.mentions,
api,
apiHolder,
binding.root.context,
credential,
lifecycleScope
lifecycleScope,
db
)
movementMethod = LinkMovementMethod.getInstance()
}
@ -199,25 +200,20 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
//region buttons
private fun activateButtons(
api: PixelfedAPI,
apiHolder: PixelfedAPIHolder,
db: AppDatabase,
lifecycleScope: LifecycleCoroutineScope,
isActivity: Boolean
){
val user = db.userDao().getActiveUser()!!
val credential = "Bearer ${user.accessToken}"
//Set the special HTML text
setDescription(api, credential, lifecycleScope)
setDescription(apiHolder, lifecycleScope, db)
//Activate onclickListeners
activateLiker(
api, credential, status?.favourited ?: false,
lifecycleScope
apiHolder, status?.favourited ?: false, lifecycleScope, db
)
activateReblogger(
api, credential, status?.reblogged ?: false,
lifecycleScope
apiHolder, status?.reblogged ?: false, lifecycleScope, db
)
if(isActivity){
@ -237,14 +233,14 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
showComments(lifecycleScope, isActivity)
activateMoreButton(api, db, lifecycleScope)
activateMoreButton(apiHolder, db, lifecycleScope)
}
private fun activateReblogger(
api: PixelfedAPI,
credential: String,
isReblogged: Boolean,
lifecycleScope: LifecycleCoroutineScope
apiHolder: PixelfedAPIHolder,
isReblogged: Boolean,
lifecycleScope: LifecycleCoroutineScope,
db: AppDatabase
) {
binding.reblogger.apply {
//Set initial button state
@ -253,12 +249,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate the button
setEventListener { _, buttonState ->
lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
if (buttonState) {
// Button is active
undoReblogPost(api, credential)
undoReblogPost(api)
} else {
// Button is inactive
reblogPost(api, credential)
reblogPost(api)
}
}
//show animation or not?
@ -267,15 +264,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun reblogPost(
api: PixelfedAPI,
credential: String
) {
private suspend fun reblogPost(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.reblogStatus(credential, it)
val resp = api.reblogStatus(it)
//Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context)
@ -290,14 +284,11 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun undoReblogPost(
api: PixelfedAPI,
credential: String,
) {
private suspend fun undoReblogPost(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.undoReblogStatus(credential, it)
val resp = api.undoReblogStatus(it)
//Update shown share count
binding.nshares.text = resp.getNShares(binding.root.context)
@ -312,7 +303,7 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private fun activateMoreButton(api: PixelfedAPI, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
private fun activateMoreButton(apiHolder: PixelfedAPIHolder, db: AppDatabase, lifecycleScope: LifecycleCoroutineScope){
binding.statusMore.setOnClickListener {
PopupMenu(it.context, it).apply {
setOnMenuItemClickListener { item ->
@ -395,7 +386,8 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
db.homePostDao().delete(id, user.user_id, user.instance_uri)
db.publicPostDao().delete(id, user.user_id, user.instance_uri)
try {
api.deleteStatus("Bearer ${user.accessToken}", id)
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
api.deleteStatus(id)
binding.root.visibility = View.GONE
} catch (exception: HttpException) {
Toast.makeText(
@ -439,10 +431,10 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
private fun activateLiker(
api: PixelfedAPI,
credential: String,
isLiked: Boolean,
lifecycleScope: LifecycleCoroutineScope
apiHolder: PixelfedAPIHolder,
isLiked: Boolean,
lifecycleScope: LifecycleCoroutineScope,
db: AppDatabase
) {
binding.liker.apply {
@ -452,12 +444,13 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
//Activate the liker
setEventListener { _, buttonState ->
lifecycleScope.launchWhenCreated {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
if (buttonState) {
// Button is active, unlike
unLikePostCall(api, credential)
unLikePostCall(api)
} else {
// Button is inactive, like
likePostCall(api, credential)
likePostCall(api)
}
}
//show animation or not?
@ -473,15 +466,16 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
if(binding.sensitiveWarning.visibility == View.GONE) {
//Check for double click
if(clicked) {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
if (binding.liker.isChecked) {
// Button is active, unlike
binding.liker.isChecked = false
unLikePostCall(api, credential)
unLikePostCall(api)
} else {
// Button is inactive, like
binding.liker.playAnimation()
binding.liker.isChecked = true
likePostCall(api, credential)
likePostCall(api)
}
} else {
clicked = true
@ -495,15 +489,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun likePostCall(
api: PixelfedAPI,
credential: String,
) {
private suspend fun likePostCall(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.likePost(credential, it)
val resp = api.likePost(it)
//Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context)
@ -518,15 +509,12 @@ class StatusViewHolder(val binding: PostFragmentBinding) : RecyclerView.ViewHold
}
}
private suspend fun unLikePostCall(
api: PixelfedAPI,
credential: String,
) {
private suspend fun unLikePostCall(api: PixelfedAPI) {
//Call the api function
status?.id?.let {
try {
val resp = api.unlikePost(credential, it)
val resp = api.unlikePost(it)
//Update shown like count and internal like toggle
binding.nlikes.text = resp.getNLikes(binding.root.context)

View File

@ -27,7 +27,6 @@ import com.h.pixeldroid.posts.feeds.cachedFeeds.ViewModelFactory
import com.h.pixeldroid.posts.parseHTMLText
import com.h.pixeldroid.posts.setTextViewFromISO8601
import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account
import com.h.pixeldroid.utils.api.objects.Notification
import com.h.pixeldroid.utils.api.objects.Status
@ -165,9 +164,9 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
fun bind(
notification: Notification?,
api: PixelfedAPI,
accessToken: String,
lifecycleScope: LifecycleCoroutineScope
api: PixelfedAPIHolder,
lifecycleScope: LifecycleCoroutineScope,
db: AppDatabase
) {
this.notification = notification
@ -208,8 +207,8 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
notification?.status?.mentions,
api,
itemView.context,
"Bearer $accessToken",
lifecycleScope
lifecycleScope,
db
)
}
@ -257,9 +256,9 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
uiModel.let {
(holder as NotificationViewHolder).bind(
it,
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
lifecycleScope
apiHolder,
lifecycleScope,
db
)
}
}

View File

@ -59,12 +59,11 @@ class NotificationsRemoteMediator @Inject constructor(
val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val accessToken = user.accessToken
val apiResponse = api.notifications("Bearer $accessToken",
max_id = max_id,
min_id = min_id,
limit = state.config.pageSize.toString(),
val apiResponse = api.notifications(
max_id = max_id,
min_id = min_id,
limit = state.config.pageSize.toString(),
)
apiResponse.forEach{it.user_id = user.user_id; it.instance_uri = user.instance_uri}

View File

@ -44,11 +44,11 @@ class HomeFeedRemoteMediator @Inject constructor(
val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val accessToken = user.accessToken
val apiResponse = api.timelineHome( "Bearer $accessToken",
max_id= max_id, min_id = min_id,
limit = state.config.pageSize.toString())
val apiResponse = api.timelineHome(
max_id= max_id,
min_id = min_id, limit = state.config.pageSize.toString()
)
val dbObjects = apiResponse.map{
HomeStatusDatabaseEntity(user.user_id, user.instance_uri, it)

View File

@ -90,7 +90,7 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status
uiModel.let {
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx)
(holder as StatusViewHolder).bind(it, apiHolder, db, lifecycleScope, displayDimensionsInPx)
}
}
}

View File

@ -55,7 +55,6 @@ class AccountListFragment : UncachedFeedFragment<Account>() {
viewModel = ViewModelProvider(this, ViewModelFactory(
FollowersContentRepository(
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
id,
following
)

View File

@ -14,7 +14,6 @@ import javax.inject.Inject
class FollowersContentRepository @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String,
private val following: Boolean,
): UncachedContentRepository<Account> {
@ -25,7 +24,7 @@ class FollowersContentRepository @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
FollowersPagingSource(api, accessToken, accountId, following)
FollowersPagingSource(api, accountId, following)
}
).flow
}

View File

@ -6,11 +6,9 @@ import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account
import retrofit2.HttpException
import java.io.IOException
import java.math.BigInteger
class FollowersPagingSource(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String,
private val following: Boolean
) : PagingSource<String, Account>() {
@ -22,17 +20,19 @@ class FollowersPagingSource(
// Laravel's paging mechanism, while Mastodon uses the Link header for pagination.
// No need to know which is which, they should ignore the non-relevant argument
if(following) {
api.followers(account_id = accountId,
authorization = "Bearer $accessToken",
api.followers(
account_id = accountId,
max_id = position,
limit = params.loadSize,
page = position,
max_id = position)
page = position
)
} else {
api.following(account_id = accountId,
authorization = "Bearer $accessToken",
api.following(
account_id = accountId,
max_id = position,
limit = params.loadSize,
page = position,
max_id = position)
page = position
)
}
val accounts = if(response.isSuccessful){

View File

@ -13,7 +13,6 @@ import javax.inject.Inject
class ProfileContentRepository @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String
) : UncachedContentRepository<Status> {
override fun getStream(): Flow<PagingData<Status>> {
@ -23,7 +22,7 @@ class ProfileContentRepository @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
ProfilePagingSource(api, accessToken, accountId)
ProfilePagingSource(api, accountId)
}
).flow
}

View File

@ -9,16 +9,15 @@ import java.io.IOException
class ProfilePagingSource(
private val api: PixelfedAPI,
private val accessToken: String,
private val accountId: String
) : PagingSource<String, Status>() {
override suspend fun load(params: LoadParams<String>): LoadResult<String, Status> {
val position = params.key
return try {
val posts = api.accountPosts("Bearer $accessToken",
account_id = accountId,
max_id = position,
limit = params.loadSize
val posts = api.accountPosts(
account_id = accountId,
max_id = position,
limit = params.loadSize
)
LoadResult.Page(

View File

@ -41,7 +41,6 @@ class SearchAccountFragment : UncachedFeedFragment<Account>() {
SearchContentRepository<Account>(
apiHolder.setDomainToCurrentUser(db),
Results.SearchType.accounts,
db.userDao().getActiveUser()!!.accessToken,
query
)
)

View File

@ -22,7 +22,6 @@ class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi
@Inject constructor(
private val api: PixelfedAPI,
private val type: Results.SearchType,
private val accessToken: String,
private val query: String,
): UncachedContentRepository<T> {
override fun getStream(): Flow<PagingData<T>> {
@ -32,7 +31,7 @@ class SearchContentRepository<T: FeedContent> @ExperimentalPagingApi
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false),
pagingSourceFactory = {
SearchPagingSource<T>(api, query, type, accessToken)
SearchPagingSource<T>(api, query, type)
}
).flow
}

View File

@ -48,7 +48,6 @@ class SearchHashtagFragment : UncachedFeedFragment<Tag>() {
SearchContentRepository<Tag>(
apiHolder.setDomainToCurrentUser(db),
Results.SearchType.hashtags,
db.userDao().getActiveUser()!!.accessToken,
query
)
)

View File

@ -15,16 +15,16 @@ class SearchPagingSource<T: FeedContent>(
private val api: PixelfedAPI,
private val query: String,
private val type: Results.SearchType,
private val accessToken: String,
) : PagingSource<Int, T>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val position = params.key
return try {
val response = api.search(authorization = "Bearer $accessToken",
offset = position?.toString(),
q = query,
val response = api.search(
type = type,
limit = params.loadSize.toString())
q = query,
limit = params.loadSize.toString(),
offset = position?.toString()
)
@Suppress("UNCHECKED_CAST")

View File

@ -45,7 +45,6 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
SearchContentRepository<Status>(
apiHolder.setDomainToCurrentUser(db),
Results.SearchType.statuses,
db.userDao().getActiveUser()!!.accessToken,
query
)
)
@ -80,7 +79,7 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status
uiModel.let {
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope, displayDimensionsInPx)
(holder as StatusViewHolder).bind(it, apiHolder, db, lifecycleScope, displayDimensionsInPx)
}
}
}

View File

@ -44,8 +44,6 @@ import java.io.IOException
class ProfileActivity : BaseActivity() {
private lateinit var pixelfedAPI : PixelfedAPI
private lateinit var accessToken : String
private lateinit var domain : String
private lateinit var accountId : String
private lateinit var binding: ActivityProfileBinding
@ -66,8 +64,6 @@ class ProfileActivity : BaseActivity() {
user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty()
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = user?.accessToken.orEmpty()
// Set profile according to given account
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
@ -77,9 +73,8 @@ class ProfileActivity : BaseActivity() {
@Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ProfileViewModelFactory(
ProfileContentRepository(
apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
accountId
apiHolder.setDomainToCurrentUser(db),
accountId
)
)
).get(FeedViewModel::class.java) as FeedViewModel<Status>
@ -124,8 +119,9 @@ class ProfileActivity : BaseActivity() {
setViews(account)
} else {
lifecycleScope.launchWhenResumed {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val myAccount: Account = try {
pixelfedAPI.verifyCredentials("Bearer $accessToken")
api.verifyCredentials()
} catch (exception: IOException) {
Log.e("ProfileActivity:", exception.toString())
return@launchWhenResumed showError()
@ -162,9 +158,10 @@ class ProfileActivity : BaseActivity() {
)
binding.descriptionTextView.text = parseHTMLText(
account.note ?: "", emptyList(), pixelfedAPI,
applicationContext, "Bearer $accessToken",
lifecycleScope
account.note ?: "", emptyList(), apiHolder,
applicationContext,
lifecycleScope,
db
)
val displayName = account.getDisplayName()
@ -235,8 +232,9 @@ class ProfileActivity : BaseActivity() {
// Get relationship between the two users (credential and this) and set followButton accordingly
lifecycleScope.launch {
try {
val relationship = pixelfedAPI.checkRelationships(
"Bearer $accessToken", listOf(account.id.orEmpty())
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val relationship = api.checkRelationships(
listOf(account.id.orEmpty())
).firstOrNull()
if(relationship != null){
@ -268,7 +266,8 @@ class ProfileActivity : BaseActivity() {
setOnClickListener {
lifecycleScope.launchWhenResumed {
try {
val rel = pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken")
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val rel = api.follow(account.id.orEmpty())
if(rel.following == true) setOnClickUnfollow(account, rel.requested == true)
else setOnClickFollow(account)
} catch (exception: IOException) {
@ -298,7 +297,8 @@ class ProfileActivity : BaseActivity() {
fun unfollow() {
lifecycleScope.launchWhenResumed {
try {
val rel = pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken")
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val rel = api.unfollow(account.id.orEmpty())
if(rel.following == false && rel.requested == false) setOnClickFollow(account)
else setOnClickUnfollow(account, rel.requested == true)
} catch (exception: IOException) {

View File

@ -20,12 +20,6 @@ import com.h.pixeldroid.posts.PostActivity
import com.h.pixeldroid.utils.BaseFragment
import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.bindingLifecycleAware
import com.mikepenz.iconics.IconicsColor
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.color
import com.mikepenz.iconics.utils.paddingDp
import com.mikepenz.iconics.utils.sizeDp
import retrofit2.HttpException
import java.io.IOException
@ -37,7 +31,6 @@ class SearchDiscoverFragment : BaseFragment() {
private lateinit var api: PixelfedAPI
private lateinit var recycler : RecyclerView
private lateinit var adapter : DiscoverRecyclerViewAdapter
private lateinit var accessToken: String
var binding: FragmentSearchBinding by bindingLifecycleAware()
@ -68,8 +61,6 @@ class SearchDiscoverFragment : BaseFragment() {
api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = db.userDao().getActiveUser()?.accessToken.orEmpty()
getDiscover()
binding.discoverRefreshLayout.setOnRefreshListener {
@ -93,7 +84,7 @@ class SearchDiscoverFragment : BaseFragment() {
private fun getDiscover() {
lifecycleScope.launchWhenCreated {
try {
val discoverPosts = api.discover("Bearer $accessToken")
val discoverPosts = api.discover()
adapter.addPosts(discoverPosts.posts)
showError(show = false)
} catch (exception: IOException) {

View File

@ -74,31 +74,23 @@ interface PixelfedAPI {
@FormUrlEncoded
@POST("/api/v1/accounts/{id}/follow")
suspend fun follow(
//The authorization header needs to be of the form "Bearer <token>"
@Path("id") statusId: String,
@Header("Authorization") authorization: String,
@Field("reblogs") reblogs : Boolean = true
) : Relationship
@POST("/api/v1/accounts/{id}/unfollow")
suspend fun unfollow(
//The authorization header needs to be of the form "Bearer <token>"
@Path("id") statusId: String,
@Header("Authorization") authorization: String
) : Relationship
@POST("api/v1/statuses/{id}/favourite")
suspend fun likePost(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Path("id") statusId: String
) : Status
@POST("/api/v1/statuses/{id}/unfavourite")
suspend fun unlikePost(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Path("id") statusId: String
) : Status
@ -106,8 +98,6 @@ interface PixelfedAPI {
@FormUrlEncoded
@POST("/api/v1/statuses")
suspend fun postStatus(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Field("status") statusText : String,
@Field("in_reply_to_id") in_reply_to_id : String? = null,
@Field("media_ids[]") media_ids : List<String> = emptyList(),
@ -124,14 +114,12 @@ interface PixelfedAPI {
@DELETE("/api/v1/statuses/{id}")
suspend fun deleteStatus(
@Header("Authorization") authorization: String,
@Path("id") statusId: String
)
@FormUrlEncoded
@POST("/api/v1/statuses/{id}/reblog")
suspend fun reblogStatus(
@Header("Authorization") authorization: String,
@Path("id") statusId: String,
@Field("visibility") visibility: String? = null
) : Status
@ -139,14 +127,12 @@ interface PixelfedAPI {
@POST("/api/v1/statuses/{id}/unreblog")
suspend fun undoReblogStatus(
@Path("id") statusId: String,
@Header("Authorization") authorization: String
) : Status
//Used in our case to retrieve comments for a given status
@GET("/api/v1/statuses/{id}/context")
suspend fun statusComments(
@Path("id") statusId: String,
@Header("Authorization") authorization: String? = null
) : Context
@GET("/api/v1/timelines/public")
@ -160,8 +146,6 @@ interface PixelfedAPI {
@GET("/api/v1/timelines/home")
suspend fun timelineHome(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Query("max_id") max_id: String? = null,
@Query("since_id") since_id: String? = null,
@Query("min_id") min_id: String? = null,
@ -171,8 +155,6 @@ interface PixelfedAPI {
@GET("/api/v2/search")
suspend fun search(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Query("account_id") account_id: String? = null,
@Query("max_id") max_id: String? = null,
@Query("min_id") min_id: String? = null,
@ -187,8 +169,6 @@ interface PixelfedAPI {
@GET("/api/v1/notifications")
suspend fun notifications(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Query("max_id") max_id: String? = null,
@Query("since_id") since_id: String? = null,
@Query("min_id") min_id: String? = null,
@ -200,13 +180,12 @@ interface PixelfedAPI {
@GET("/api/v1/accounts/verify_credentials")
suspend fun verifyCredentials(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String
@Header("Authorization") authorization: String? = null
): Account
@GET("/api/v1/accounts/{id}/statuses")
suspend fun accountPosts(
@Header("Authorization") authorization: String,
@Path("id") account_id: String,
@Query("min_id") min_id: String? = null,
@Query("max_id") max_id: String?,
@ -215,14 +194,12 @@ interface PixelfedAPI {
@GET("/api/v1/accounts/relationships")
suspend fun checkRelationships(
@Header("Authorization") authorization : String,
@Query("id[]") account_ids : List<String>
) : List<Relationship>
@GET("/api/v1/accounts/{id}/followers")
suspend fun followers(
@Path("id") account_id: String,
@Header("Authorization") authorization: String,
@Query("max_id") max_id: String? = null,
@Query("since_id") since_id: String? = null,
@Query("limit") limit: Number? = null,
@ -232,7 +209,6 @@ interface PixelfedAPI {
@GET("/api/v1/accounts/{id}/following")
suspend fun following(
@Path("id") account_id: String,
@Header("Authorization") authorization: String,
@Query("max_id") max_id: String? = null,
@Query("since_id") since_id: String? = null,
@Query("limit") limit: Number? = 40,
@ -241,36 +217,29 @@ interface PixelfedAPI {
@GET("/api/v1/accounts/{id}")
suspend fun getAccount(
@Header("Authorization") authorization: String,
@Path("id") accountId : String
): Account
@GET("/api/v1/statuses/{id}")
suspend fun getStatus(
@Header("Authorization") authorization: String,
@Path("id") accountId : String
): Status
@Multipart
@POST("/api/v1/media")
fun mediaUpload(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Part description: MultipartBody.Part? = null,
@Part file: MultipartBody.Part
): Observable<Attachment>
// get discover
@GET("/api/v2/discover/posts")
suspend fun discover(
@Header("Authorization") authorization: String
) : DiscoverPosts
suspend fun discover() : DiscoverPosts
@FormUrlEncoded
@POST("/api/v1/reports")
@JvmSuppressWildcards
suspend fun report(
@Header("Authorization") authorization: String,
@Field("account_id") account_id: String,
@Field("status_ids") status_ids: List<Status>,
@Field("comment") comment: String,

View File

@ -6,13 +6,7 @@ import android.util.Log
import androidx.core.content.ContextCompat.startActivity
import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.utils.api.PixelfedAPI
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import retrofit2.Call
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import java.io.IOException
import java.io.Serializable
@ -57,9 +51,9 @@ data class Account(
/**
* @brief Opens an activity of the profile with the given id
*/
suspend fun openAccountFromId(id: String, api : PixelfedAPI, context: Context, credential: String) {
suspend fun openAccountFromId(id: String, api : PixelfedAPI, context: Context) {
val account = try {
api.getAccount(credential, id)
api.getAccount(id)
} catch (exception: IOException) {
Log.e("GET ACCOUNT ERROR", exception.toString())
return

View File

@ -8,8 +8,8 @@ interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: UserDatabaseEntity)
@Query("UPDATE users SET accessToken = :accessToken WHERE user_id = :id and instance_uri = :instance_uri")
fun updateAccessToken(accessToken: String, id: String, instance_uri: String)
@Query("UPDATE users SET accessToken = :accessToken, refreshToken = :refreshToken WHERE user_id = :id and instance_uri = :instance_uri")
fun updateAccessToken(accessToken: String, refreshToken: String, id: String, instance_uri: String)
@Query("SELECT * FROM users")
fun getAll(): List<UserDatabaseEntity>

View File

@ -1,6 +1,7 @@
package com.h.pixeldroid.utils.di
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Token
import com.h.pixeldroid.utils.db.AppDatabase
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
import dagger.Module
@ -22,17 +23,30 @@ class APIModule{
}
}
class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase) : Authenticator {
class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase, val apiHolder: PixelfedAPIHolder) : Authenticator {
private val pixelfedAPI = PixelfedAPI.createFromUrl(user.instance_uri)
// Returns the number of tries for this response by walking through the priorResponses
private fun Response.responseCount(): Int {
var result = 1
var response: Response? = priorResponse
while (response != null) {
result++
response = response.priorResponse
}
return result
}
override fun authenticate(route: Route?, response: Response): Request? {
if (response.request.header("Authorization") != null) {
return null // Give up, we've already failed to authenticate.
if (response.responseCount() > 3) {
return null // Give up, we've already failed to authenticate a couple times
}
// Refresh the access_token using a synchronous api request
val newAccessToken: String? = try {
val newAccessToken: Token = try {
runBlocking {
pixelfedAPI.obtainToken(
scope = "",
@ -40,19 +54,25 @@ class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase) : Au
refresh_token = user.refreshToken,
client_id = user.clientId,
client_secret = user.clientSecret
).access_token
)
}
}catch (e: Exception){
null
return null
}
if (newAccessToken != null) {
db.userDao().updateAccessToken(newAccessToken, user.user_id, user.instance_uri)
// Save the new access token and refresh token
if (newAccessToken.access_token != null && newAccessToken.refresh_token != null) {
db.userDao().updateAccessToken(
newAccessToken.access_token,
newAccessToken.refresh_token,
user.user_id, user.instance_uri
)
apiHolder.setDomainToCurrentUser(db)
}
// Add new header to rejected request and retry it
return response.request.newBuilder()
.header("Authorization", "Bearer ${newAccessToken.orEmpty()}")
.header("Authorization", "Bearer ${newAccessToken.access_token.orEmpty()}")
.build()
}
}
@ -61,6 +81,7 @@ class PixelfedAPIHolder(db: AppDatabase?){
private val intermediate: Retrofit.Builder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
var api: PixelfedAPI? =
db?.userDao()?.getActiveUser()?.let {
setDomainToCurrentUser(db, it)
@ -73,10 +94,11 @@ class PixelfedAPIHolder(db: AppDatabase?){
val newAPI = intermediate
.baseUrl(user.instance_uri)
.client(
OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user, db))
OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user, db, this))
.addInterceptor {
it.request().newBuilder().run {
header("Accept", "application/json")
header("Authorization", "Bearer ${user.accessToken}")
it.proceed(build())
}
}.build()

View File

@ -4,7 +4,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.junit.WireMockRule
import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.*
import io.reactivex.Single
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Rule
@ -91,7 +90,7 @@ class APIUnitTest {
statuses = PixelfedAPI.createFromUrl("http://localhost:8089")
.timelinePublic(null, null, null, null, null)
statusesHome = PixelfedAPI.createFromUrl("http://localhost:8089")
.timelineHome("abc", null, null, null,null, null)
.timelineHome(null, null, null, null, null)
}