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() { private fun getUpdatedAccount() {
if (hasInternet(applicationContext)) { 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 { lifecycleScope.launchWhenCreated {
try { 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) addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
fillDrawerAccountInfo(account.id!!) fillDrawerAccountInfo(account.id!!)
} catch (exception: IOException) { } 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.carousel.ImageCarousel
import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity import com.h.pixeldroid.postCreation.photoEdit.PhotoEditActivity
import com.h.pixeldroid.utils.BaseActivity 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.api.objects.Attachment
import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity import com.h.pixeldroid.utils.db.entities.InstanceDatabaseEntity
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
@ -58,8 +57,6 @@ data class PhotoData(
class PostCreationActivity : BaseActivity() { class PostCreationActivity : BaseActivity() {
private lateinit var accessToken: String
private var user: UserDatabaseEntity? = null private var user: UserDatabaseEntity? = null
private lateinit var instance: InstanceDatabaseEntity private lateinit var instance: InstanceDatabaseEntity
@ -85,8 +82,6 @@ class PostCreationActivity : BaseActivity() {
// get image URIs // get image URIs
intent.clipData?.let { addPossibleImages(it) } intent.clipData?.let { addPossibleImages(it) }
accessToken = user?.accessToken.orEmpty()
val carousel: ImageCarousel = binding.carousel val carousel: ImageCarousel = binding.carousel
carousel.addData(photoData.map { CarouselItem(it.imageUri) }) carousel.addData(photoData.map { CarouselItem(it.imageUri) })
carousel.layoutCarouselCallback = { carousel.layoutCarouselCallback = {
@ -338,7 +333,7 @@ class PostCreationActivity : BaseActivity() {
val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) } val description = data.imageDescription?.let { MultipartBody.Part.createFormData("description", it) }
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) 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 postSub = inter
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@ -383,7 +378,6 @@ class PostCreationActivity : BaseActivity() {
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
api.postStatus( api.postStatus(
authorization = "Bearer $accessToken",
statusText = description, statusText = description,
media_ids = photoData.mapNotNull { it.uploadId }.toList() media_ids = photoData.mapNotNull { it.uploadId }.toList()
) )

View File

@ -12,13 +12,13 @@ import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.text.toSpanned import androidx.core.text.toSpanned
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import com.h.pixeldroid.R import com.h.pixeldroid.R
import com.h.pixeldroid.utils.api.PixelfedAPI import com.h.pixeldroid.utils.api.PixelfedAPI
import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId
import com.h.pixeldroid.utils.api.objects.Mention 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.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import java.text.ParseException import java.text.ParseException
@ -50,12 +50,12 @@ fun getDomain(urlString: String?): String {
} }
fun parseHTMLText( fun parseHTMLText(
text : String, text: String,
mentions: List<Mention>?, mentions: List<Mention>?,
api : PixelfedAPI, apiHolder: PixelfedAPIHolder,
context: Context, context: Context,
credential: String, lifecycleScope: LifecycleCoroutineScope,
lifecycleScope: LifecycleCoroutineScope db: AppDatabase
) : Spanned { ) : Spanned {
//Convert text to spannable //Convert text to spannable
val content = fromHtml(text) val content = fromHtml(text)
@ -108,7 +108,8 @@ fun parseHTMLText(
Log.e("MENTION", "CLICKED") Log.e("MENTION", "CLICKED")
//Retrieve the account for the given profile //Retrieve the account for the given profile
lifecycleScope.launchWhenCreated { 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() { class PostActivity : BaseActivity() {
lateinit var domain : String lateinit var domain : String
private lateinit var accessToken : String
private lateinit var binding: ActivityPostBinding private lateinit var binding: ActivityPostBinding
@ -45,17 +44,15 @@ class PostActivity : BaseActivity() {
val user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty() domain = user?.instance_uri.orEmpty()
accessToken = user?.accessToken.orEmpty()
supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName()) supportActionBar?.title = getString(R.string.post_title).format(status.account?.getDisplayName())
val holder = StatusViewHolder(binding.postFragmentSingle) 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()
activateCommenter(credential)
if(viewComments || postComment){ if(viewComments || postComment){
//Scroll already down as much as possible (since comments are not loaded yet) //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 // 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 { binding.postFragmentSingle.viewComments.setOnClickListener {
retrieveComments(apiHolder.api!!, credential) retrieveComments(apiHolder.api!!)
} }
} }
@ -80,7 +77,7 @@ class PostActivity : BaseActivity() {
return true return true
} }
private fun activateCommenter(credential: String) { private fun activateCommenter() {
//Activate commenter //Activate commenter
binding.submitComment.setOnClickListener { binding.submitComment.setOnClickListener {
val textIn = binding.editComment.text val textIn = binding.editComment.text
@ -94,15 +91,14 @@ class PostActivity : BaseActivity() {
} else { } else {
//Post the comment //Post the comment
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
apiHolder.api?.let { it1 -> postComment(it1, credential) } apiHolder.api?.let { it1 -> postComment(it1) }
} }
} }
} }
} }
private fun addComment(context: Context, commentContainer: LinearLayout, private fun addComment(context: Context, commentContainer: LinearLayout,
commentUsername: String, commentContent: String, mentions: List<Mention>, commentUsername: String, commentContent: String, mentions: List<Mention>) {
credential: String) {
val itemBinding = CommentBinding.inflate( val itemBinding = CommentBinding.inflate(
@ -113,25 +109,29 @@ class PostActivity : BaseActivity() {
itemBinding.commentText.text = parseHTMLText( itemBinding.commentText.text = parseHTMLText(
commentContent, commentContent,
mentions, mentions,
apiHolder.api!!, apiHolder,
context, context,
credential, lifecycleScope,
lifecycleScope db
) )
} }
private fun retrieveComments(api: PixelfedAPI, credential: String) { private fun retrieveComments(api: PixelfedAPI) {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
status.id.let { status.id.let {
try { try {
val statuses = api.statusComments(it, credential).descendants val statuses = api.statusComments(it).descendants
binding.commentContainer.removeAllViews() binding.commentContainer.removeAllViews()
//Create the new views for each comment //Create the new views for each comment
for (status in statuses) { for (status in statuses) {
addComment(binding.root.context, binding.commentContainer, status.account!!.username!!, addComment(
status.content!!, status.mentions.orEmpty(), credential binding.root.context,
binding.commentContainer,
status.account!!.username!!,
status.content!!,
status.mentions.orEmpty()
) )
} }
binding.commentContainer.visibility = View.VISIBLE binding.commentContainer.visibility = View.VISIBLE
@ -149,19 +149,18 @@ class PostActivity : BaseActivity() {
private suspend fun postComment( private suspend fun postComment(
api: PixelfedAPI, api: PixelfedAPI,
credential: String,
) { ) {
val textIn = binding.editComment.text val textIn = binding.editComment.text
val nonNullText = textIn.toString() val nonNullText = textIn.toString()
status.id.let { status.id.let {
try { try {
val response = api.postStatus(credential, nonNullText, it) val response = api.postStatus(nonNullText, it)
binding.commentIn.visibility = View.GONE binding.commentIn.visibility = View.GONE
//Add the comment to the comment section //Add the comment to the comment section
addComment( addComment(
binding.root.context, binding.commentContainer, response.account!!.username!!, binding.root.context, binding.commentContainer, response.account!!.username!!,
response.content!!, response.mentions.orEmpty(), credential response.content!!, response.mentions.orEmpty()
) )
Toast.makeText( Toast.makeText(

View File

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

View File

@ -59,12 +59,11 @@ class NotificationsRemoteMediator @Inject constructor(
val user = db.userDao().getActiveUser() val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists")) ?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val accessToken = user.accessToken
val apiResponse = api.notifications("Bearer $accessToken", val apiResponse = api.notifications(
max_id = max_id, max_id = max_id,
min_id = min_id, min_id = min_id,
limit = state.config.pageSize.toString(), limit = state.config.pageSize.toString(),
) )
apiResponse.forEach{it.user_id = user.user_id; it.instance_uri = user.instance_uri} 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() val user = db.userDao().getActiveUser()
?: return MediatorResult.Error(NullPointerException("No active user exists")) ?: return MediatorResult.Error(NullPointerException("No active user exists"))
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val accessToken = user.accessToken
val apiResponse = api.timelineHome( "Bearer $accessToken", val apiResponse = api.timelineHome(
max_id= max_id, min_id = min_id, max_id= max_id,
limit = state.config.pageSize.toString()) min_id = min_id, limit = state.config.pageSize.toString()
)
val dbObjects = apiResponse.map{ val dbObjects = apiResponse.map{
HomeStatusDatabaseEntity(user.user_id, user.instance_uri, it) 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) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status val uiModel = getItem(position) as Status
uiModel.let { 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( viewModel = ViewModelProvider(this, ViewModelFactory(
FollowersContentRepository( FollowersContentRepository(
apiHolder.setDomainToCurrentUser(db), apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken,
id, id,
following following
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,6 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
SearchContentRepository<Status>( SearchContentRepository<Status>(
apiHolder.setDomainToCurrentUser(db), apiHolder.setDomainToCurrentUser(db),
Results.SearchType.statuses, Results.SearchType.statuses,
db.userDao().getActiveUser()!!.accessToken,
query query
) )
) )
@ -80,7 +79,7 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val uiModel = getItem(position) as Status val uiModel = getItem(position) as Status
uiModel.let { 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() { class ProfileActivity : BaseActivity() {
private lateinit var pixelfedAPI : PixelfedAPI
private lateinit var accessToken : String
private lateinit var domain : String private lateinit var domain : String
private lateinit var accountId : String private lateinit var accountId : String
private lateinit var binding: ActivityProfileBinding private lateinit var binding: ActivityProfileBinding
@ -66,8 +64,6 @@ class ProfileActivity : BaseActivity() {
user = db.userDao().getActiveUser() user = db.userDao().getActiveUser()
domain = user?.instance_uri.orEmpty() domain = user?.instance_uri.orEmpty()
pixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = user?.accessToken.orEmpty()
// Set profile according to given account // Set profile according to given account
val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account? val account = intent.getSerializableExtra(Account.ACCOUNT_TAG) as Account?
@ -77,9 +73,8 @@ class ProfileActivity : BaseActivity() {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
viewModel = ViewModelProvider(this, ProfileViewModelFactory( viewModel = ViewModelProvider(this, ProfileViewModelFactory(
ProfileContentRepository( ProfileContentRepository(
apiHolder.setDomainToCurrentUser(db), apiHolder.setDomainToCurrentUser(db),
db.userDao().getActiveUser()!!.accessToken, accountId
accountId
) )
) )
).get(FeedViewModel::class.java) as FeedViewModel<Status> ).get(FeedViewModel::class.java) as FeedViewModel<Status>
@ -124,8 +119,9 @@ class ProfileActivity : BaseActivity() {
setViews(account) setViews(account)
} else { } else {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
val myAccount: Account = try { val myAccount: Account = try {
pixelfedAPI.verifyCredentials("Bearer $accessToken") api.verifyCredentials()
} catch (exception: IOException) { } catch (exception: IOException) {
Log.e("ProfileActivity:", exception.toString()) Log.e("ProfileActivity:", exception.toString())
return@launchWhenResumed showError() return@launchWhenResumed showError()
@ -162,9 +158,10 @@ class ProfileActivity : BaseActivity() {
) )
binding.descriptionTextView.text = parseHTMLText( binding.descriptionTextView.text = parseHTMLText(
account.note ?: "", emptyList(), pixelfedAPI, account.note ?: "", emptyList(), apiHolder,
applicationContext, "Bearer $accessToken", applicationContext,
lifecycleScope lifecycleScope,
db
) )
val displayName = account.getDisplayName() val displayName = account.getDisplayName()
@ -235,8 +232,9 @@ class ProfileActivity : BaseActivity() {
// Get relationship between the two users (credential and this) and set followButton accordingly // Get relationship between the two users (credential and this) and set followButton accordingly
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val relationship = pixelfedAPI.checkRelationships( val api: PixelfedAPI = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
"Bearer $accessToken", listOf(account.id.orEmpty()) val relationship = api.checkRelationships(
listOf(account.id.orEmpty())
).firstOrNull() ).firstOrNull()
if(relationship != null){ if(relationship != null){
@ -268,7 +266,8 @@ class ProfileActivity : BaseActivity() {
setOnClickListener { setOnClickListener {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
try { 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) if(rel.following == true) setOnClickUnfollow(account, rel.requested == true)
else setOnClickFollow(account) else setOnClickFollow(account)
} catch (exception: IOException) { } catch (exception: IOException) {
@ -298,7 +297,8 @@ class ProfileActivity : BaseActivity() {
fun unfollow() { fun unfollow() {
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
try { 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) if(rel.following == false && rel.requested == false) setOnClickFollow(account)
else setOnClickUnfollow(account, rel.requested == true) else setOnClickUnfollow(account, rel.requested == true)
} catch (exception: IOException) { } 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.BaseFragment
import com.h.pixeldroid.utils.ImageConverter import com.h.pixeldroid.utils.ImageConverter
import com.h.pixeldroid.utils.bindingLifecycleAware 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 retrofit2.HttpException
import java.io.IOException import java.io.IOException
@ -37,7 +31,6 @@ class SearchDiscoverFragment : BaseFragment() {
private lateinit var api: PixelfedAPI private lateinit var api: PixelfedAPI
private lateinit var recycler : RecyclerView private lateinit var recycler : RecyclerView
private lateinit var adapter : DiscoverRecyclerViewAdapter private lateinit var adapter : DiscoverRecyclerViewAdapter
private lateinit var accessToken: String
var binding: FragmentSearchBinding by bindingLifecycleAware() var binding: FragmentSearchBinding by bindingLifecycleAware()
@ -68,8 +61,6 @@ class SearchDiscoverFragment : BaseFragment() {
api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db) api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
accessToken = db.userDao().getActiveUser()?.accessToken.orEmpty()
getDiscover() getDiscover()
binding.discoverRefreshLayout.setOnRefreshListener { binding.discoverRefreshLayout.setOnRefreshListener {
@ -93,7 +84,7 @@ class SearchDiscoverFragment : BaseFragment() {
private fun getDiscover() { private fun getDiscover() {
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
try { try {
val discoverPosts = api.discover("Bearer $accessToken") val discoverPosts = api.discover()
adapter.addPosts(discoverPosts.posts) adapter.addPosts(discoverPosts.posts)
showError(show = false) showError(show = false)
} catch (exception: IOException) { } catch (exception: IOException) {

View File

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

View File

@ -6,13 +6,7 @@ import android.util.Log
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import com.h.pixeldroid.profile.ProfileActivity import com.h.pixeldroid.profile.ProfileActivity
import com.h.pixeldroid.utils.api.PixelfedAPI 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.HttpException
import retrofit2.Response
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
@ -57,9 +51,9 @@ data class Account(
/** /**
* @brief Opens an activity of the profile with the given id * @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 { val account = try {
api.getAccount(credential, id) api.getAccount(id)
} catch (exception: IOException) { } catch (exception: IOException) {
Log.e("GET ACCOUNT ERROR", exception.toString()) Log.e("GET ACCOUNT ERROR", exception.toString())
return return

View File

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

View File

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

View File

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