Use coroutines
This commit is contained in:
parent
178ae0b392
commit
3a91b02e55
@ -9,6 +9,7 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.*
|
||||
@ -17,24 +18,22 @@ import com.h.pixeldroid.utils.db.storeInstance
|
||||
import com.h.pixeldroid.utils.hasInternet
|
||||
import com.h.pixeldroid.utils.normalizeDomain
|
||||
import com.h.pixeldroid.utils.openUrl
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.HttpUrl
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
Overview of the flow of the login process: (boxes are requests done in parallel,
|
||||
since they do not depend on each other)
|
||||
|
||||
_________________________________
|
||||
|[PixelfedAPI.registerApplication]|
|
||||
|[PixelfedAPI.registerApplicationAsync]|
|
||||
|[PixelfedAPI.wellKnownNodeInfo] |
|
||||
̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅̅ +----> [PixelfedAPI.nodeInfoSchema]
|
||||
+----> [promptOAuth]
|
||||
@ -130,81 +129,72 @@ class LoginActivity : BaseActivity() {
|
||||
loadingAnimation(true)
|
||||
|
||||
pixelfedAPI = PixelfedAPI.createFromUrl(normalizedDomain)
|
||||
|
||||
Single.zip(
|
||||
pixelfedAPI.registerApplication(
|
||||
appName,"$oauthScheme://$PACKAGE_ID", SCOPE
|
||||
),
|
||||
pixelfedAPI.wellKnownNodeInfo(),
|
||||
{ application, nodeInfoJRD ->
|
||||
// we get here when both results have come in:
|
||||
Pair(application, nodeInfoJRD)
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : SingleObserver<Pair<Application, NodeInfoJRD>> {
|
||||
override fun onSuccess(pair: Pair<Application, NodeInfoJRD>) {
|
||||
val (credentials, nodeInfoJRD) = pair
|
||||
val clientId = credentials.client_id ?: return failedRegistration()
|
||||
preferences.edit()
|
||||
.putString("domain", normalizedDomain)
|
||||
.putString("clientID", clientId)
|
||||
.putString("clientSecret", credentials.client_secret)
|
||||
.apply()
|
||||
|
||||
//c.f. https://nodeinfo.diaspora.software/protocol.html for more info
|
||||
val nodeInfoSchemaUrl = nodeInfoJRD.links.firstOrNull {
|
||||
it.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
||||
}?.href ?: return failedRegistration(getString(R.string.instance_error))
|
||||
|
||||
nodeInfoSchema(normalizedDomain, clientId, nodeInfoSchemaUrl)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val credentialsDeferred = async {
|
||||
pixelfedAPI.registerApplication(
|
||||
appName, "$oauthScheme://$PACKAGE_ID", SCOPE
|
||||
)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
//Error in any of the two requests will get to this
|
||||
Log.e("registerAppToServer", e.message.toString())
|
||||
failedRegistration()
|
||||
}
|
||||
val nodeInfoJRD = pixelfedAPI.wellKnownNodeInfo()
|
||||
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
})
|
||||
val credentials = credentialsDeferred.await()
|
||||
|
||||
val clientId = credentials.client_id ?: return@launch failedRegistration()
|
||||
preferences.edit()
|
||||
.putString("domain", normalizedDomain)
|
||||
.putString("clientID", clientId)
|
||||
.putString("clientSecret", credentials.client_secret)
|
||||
.apply()
|
||||
|
||||
|
||||
// c.f. https://nodeinfo.diaspora.software/protocol.html for more info
|
||||
val nodeInfoSchemaUrl = nodeInfoJRD.links.firstOrNull {
|
||||
it.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
||||
}?.href ?: return@launch failedRegistration(getString(R.string.instance_error))
|
||||
|
||||
nodeInfoSchema(normalizedDomain, clientId, nodeInfoSchemaUrl)
|
||||
} catch (exception: IOException) {
|
||||
return@launch failedRegistration()
|
||||
} catch (exception: HttpException) {
|
||||
return@launch failedRegistration()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun nodeInfoSchema(
|
||||
private suspend fun nodeInfoSchema(
|
||||
normalizedDomain: String,
|
||||
clientId: String,
|
||||
nodeInfoSchemaUrl: String
|
||||
) {
|
||||
pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl).enqueue(object : Callback<NodeInfo> {
|
||||
override fun onResponse(call: Call<NodeInfo>, response: Response<NodeInfo>) {
|
||||
if (response.body() == null || !response.isSuccessful) {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
val nodeInfo = response.body() as NodeInfo
|
||||
val nodeInfo = try {
|
||||
pixelfedAPI.nodeInfoSchema(nodeInfoSchemaUrl)
|
||||
} catch (exception: IOException) {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
} catch (exception: HttpException) {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
|
||||
if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) {
|
||||
val builder = AlertDialog.Builder(this@LoginActivity)
|
||||
builder.apply {
|
||||
setMessage(R.string.instance_not_pixelfed_warning)
|
||||
setPositiveButton(R.string.instance_not_pixelfed_continue) { _, _ ->
|
||||
promptOAuth(normalizedDomain, clientId)
|
||||
}
|
||||
setNegativeButton(R.string.instance_not_pixelfed_cancel) { _, _ ->
|
||||
loadingAnimation(false)
|
||||
wipeSharedSettings()
|
||||
}
|
||||
}
|
||||
// Create the AlertDialog
|
||||
builder.show()
|
||||
return
|
||||
} else {
|
||||
if (!nodeInfo.software?.name.orEmpty().contains("pixelfed")) {
|
||||
val builder = AlertDialog.Builder(this@LoginActivity)
|
||||
builder.apply {
|
||||
setMessage(R.string.instance_not_pixelfed_warning)
|
||||
setPositiveButton(R.string.instance_not_pixelfed_continue) { _, _ ->
|
||||
promptOAuth(normalizedDomain, clientId)
|
||||
}
|
||||
setNegativeButton(R.string.instance_not_pixelfed_cancel) { _, _ ->
|
||||
loadingAnimation(false)
|
||||
wipeSharedSettings()
|
||||
}
|
||||
}
|
||||
override fun onFailure(call: Call<NodeInfo>, t: Throwable) {
|
||||
failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
})
|
||||
// Create the AlertDialog
|
||||
builder.show()
|
||||
} else {
|
||||
promptOAuth(normalizedDomain, clientId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -233,43 +223,41 @@ class LoginActivity : BaseActivity() {
|
||||
//Successful authorization
|
||||
pixelfedAPI = PixelfedAPI.createFromUrl(domain)
|
||||
|
||||
//TODO check why we can't do onErrorReturn { null } which would make more sense ¯\_(ツ)_/¯
|
||||
//Also, maybe find a nicer way to do this, this feels hacky (although it can work fine)
|
||||
val nullInstance = Instance(null, null, null, null, null, null, null, null)
|
||||
val nullToken = Token(null, null, null, null, null)
|
||||
|
||||
Single.zip(
|
||||
pixelfedAPI.instance().onErrorReturn { nullInstance },
|
||||
pixelfedAPI.obtainToken(
|
||||
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
|
||||
"authorization_code"
|
||||
).onErrorReturn { nullToken },
|
||||
{ instance, token ->
|
||||
// we get here when all results have come in:
|
||||
Pair(instance, token)
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : SingleObserver<Pair<Instance, Token>> {
|
||||
override fun onSuccess(triple: Pair<Instance, Token>) {
|
||||
val (instance, token) = triple
|
||||
if(token == nullToken || token.access_token == null){
|
||||
return failedRegistration(getString(R.string.token_error))
|
||||
} else if(instance == nullInstance || instance.uri == null){
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
|
||||
storeInstance(db, instance)
|
||||
storeUser(token.access_token, token.refresh_token, clientId, clientSecret, instance.uri)
|
||||
wipeSharedSettings()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val instanceDeferred = async {
|
||||
pixelfedAPI.instance()
|
||||
}
|
||||
val token = pixelfedAPI.obtainToken(
|
||||
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
|
||||
"authorization_code"
|
||||
)
|
||||
if (token.access_token == null) {
|
||||
return@launch failedRegistration(getString(R.string.token_error))
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e("saveUserAndInstance", e.message.toString())
|
||||
failedRegistration(getString(R.string.token_error))
|
||||
val instance = instanceDeferred.await()
|
||||
|
||||
if (instance.uri == null) {
|
||||
return@launch failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
override fun onSubscribe(d: Disposable) {}
|
||||
})
|
||||
|
||||
storeInstance(db, instance)
|
||||
storeUser(
|
||||
token.access_token,
|
||||
token.refresh_token,
|
||||
clientId,
|
||||
clientSecret,
|
||||
instance.uri
|
||||
)
|
||||
wipeSharedSettings()
|
||||
} catch (exception: IOException) {
|
||||
return@launch failedRegistration(getString(R.string.token_error))
|
||||
|
||||
} catch (exception: HttpException) {
|
||||
return@launch failedRegistration(getString(R.string.token_error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun failedRegistration(message: String = getString(R.string.registration_failed)) {
|
||||
@ -294,32 +282,29 @@ class LoginActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeUser(accessToken: String, refreshToken: String?, clientId: String, clientSecret: String, instance: String) {
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.body() != null && response.isSuccessful) {
|
||||
db.userDao().deActivateActiveUsers()
|
||||
val user = response.body() as Account
|
||||
addUser(
|
||||
db,
|
||||
user,
|
||||
instance,
|
||||
activeUser = true,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
apiHolder.setDomainToCurrentUser(db)
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
}
|
||||
})
|
||||
private suspend fun storeUser(accessToken: String, refreshToken: String?, clientId: String, clientSecret: String, instance: String) {
|
||||
try {
|
||||
val user = pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
db.userDao().deActivateActiveUsers()
|
||||
addUser(
|
||||
db,
|
||||
user,
|
||||
instance,
|
||||
activeUser = true,
|
||||
accessToken = accessToken,
|
||||
refreshToken = refreshToken,
|
||||
clientId = clientId,
|
||||
clientSecret = clientSecret
|
||||
)
|
||||
apiHolder.setDomainToCurrentUser(db)
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
} catch (exception: IOException) {
|
||||
return failedRegistration(getString(R.string.verify_credentials))
|
||||
} catch (exception: HttpException) {
|
||||
return failedRegistration(getString(R.string.verify_credentials))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.bumptech.glide.Glide
|
||||
@ -42,7 +43,9 @@ import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.ligi.tracedroid.sending.TraceDroidEmailSender
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
|
||||
@ -188,23 +191,18 @@ class MainActivity : BaseActivity() {
|
||||
val clientId = user?.clientId.orEmpty()
|
||||
val clientSecret = user?.clientSecret.orEmpty()
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
api.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(
|
||||
call: Call<Account>,
|
||||
response: Response<Account>
|
||||
) {
|
||||
if (response.body() != null && response.isSuccessful) {
|
||||
val account = response.body() as Account
|
||||
addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
|
||||
fillDrawerAccountInfo(account.id!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("DRAWER ACCOUNT:", t.toString())
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
val account = api.verifyCredentials("Bearer $accessToken")
|
||||
addUser(db, account, domain, accessToken = accessToken, refreshToken = refreshToken, clientId = clientId, clientSecret = clientSecret)
|
||||
fillDrawerAccountInfo(account.id!!)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("ACCOUNT UPDATE:", exception.toString())
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("ACCOUNT UPDATE:", exception.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ class PostFragment : BaseFragment() {
|
||||
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
|
||||
val api = apiHolder.api ?: apiHolder.setDomain(user)
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
|
||||
val holder = StatusViewHolder(root)
|
||||
|
||||
|
@ -89,8 +89,7 @@ class PostFeedFragment<T: FeedContentDatabase>: CachedFeedFragment<T>() {
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position) as Status
|
||||
uiModel.let {
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,8 +79,7 @@ class SearchPostsFragment : UncachedFeedFragment<Status>() {
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position) as Status
|
||||
uiModel.let {
|
||||
val user = db.userDao().getActiveUser()!!
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomain(user), db, lifecycleScope)
|
||||
(holder as StatusViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db, lifecycleScope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.widget.*
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
@ -22,9 +23,12 @@ import com.h.pixeldroid.posts.parseHTMLText
|
||||
import com.h.pixeldroid.utils.BaseActivity
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.openUrl
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class ProfileActivity : BaseActivity() {
|
||||
private lateinit var pixelfedAPI : PixelfedAPI
|
||||
@ -75,25 +79,19 @@ class ProfileActivity : BaseActivity() {
|
||||
setViews(account)
|
||||
setPosts(account)
|
||||
} else {
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.code() == 200) {
|
||||
val myAccount = response.body()!!
|
||||
|
||||
setViews(myAccount)
|
||||
// Populate profile page with user's posts
|
||||
setPosts(myAccount)
|
||||
} else {
|
||||
showError()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("ProfileActivity:", t.toString())
|
||||
showError()
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenResumed {
|
||||
val myAccount: Account = try {
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
} catch (exception: IOException) {
|
||||
Log.e("ProfileActivity:", exception.toString())
|
||||
return@launchWhenResumed showError()
|
||||
} catch (exception: HttpException) {
|
||||
return@launchWhenResumed showError()
|
||||
}
|
||||
setViews(myAccount)
|
||||
// Populate profile page with user's posts
|
||||
setPosts(myAccount)
|
||||
}
|
||||
}
|
||||
|
||||
//if we aren't viewing our own account, activate follow button
|
||||
@ -244,40 +242,35 @@ class ProfileActivity : BaseActivity() {
|
||||
*/
|
||||
private fun activateFollow(account: Account) {
|
||||
// Get relationship between the two users (credential and this) and set followButton accordingly
|
||||
pixelfedAPI.checkRelationships("Bearer $accessToken", listOf(account.id.orEmpty()))
|
||||
.enqueue(object : Callback<List<Relationship>> {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val relationship = pixelfedAPI.checkRelationships(
|
||||
"Bearer $accessToken", listOf(account.id.orEmpty())
|
||||
).firstOrNull()
|
||||
|
||||
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
|
||||
Log.e("FOLLOW ERROR", t.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_status_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
if(relationship != null){
|
||||
val followButton = findViewById<Button>(R.id.followButton)
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<List<Relationship>>,
|
||||
response: Response<List<Relationship>>
|
||||
) {
|
||||
if (response.code() == 200) {
|
||||
if (response.body()!!.isNotEmpty()) {
|
||||
val followButton = findViewById<Button>(R.id.followButton)
|
||||
|
||||
if (response.body()!![0].following) {
|
||||
setOnClickUnfollow(account)
|
||||
} else {
|
||||
setOnClickFollow(account)
|
||||
}
|
||||
followButton.visibility = View.VISIBLE
|
||||
}
|
||||
if (relationship.following) {
|
||||
setOnClickUnfollow(account)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_button_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
setOnClickFollow(account)
|
||||
}
|
||||
followButton.visibility = View.VISIBLE
|
||||
}
|
||||
})
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_status_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_button_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setOnClickFollow(account: Account) {
|
||||
@ -286,31 +279,23 @@ class ProfileActivity : BaseActivity() {
|
||||
followButton.setText(R.string.follow)
|
||||
|
||||
followButton.setOnClickListener {
|
||||
pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
.enqueue(object : Callback<Relationship> {
|
||||
|
||||
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||
Log.e("FOLLOW ERROR", t.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<Relationship>,
|
||||
response: Response<Relationship>
|
||||
) {
|
||||
if (response.code() == 200) {
|
||||
setOnClickUnfollow(account)
|
||||
} else if (response.code() == 403) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.action_not_allowed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenResumed {
|
||||
try {
|
||||
pixelfedAPI.follow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
setOnClickUnfollow(account)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,31 +305,24 @@ class ProfileActivity : BaseActivity() {
|
||||
followButton.setText(R.string.unfollow)
|
||||
|
||||
followButton.setOnClickListener {
|
||||
pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
.enqueue(object : Callback<Relationship> {
|
||||
|
||||
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||
Log.e("UNFOLLOW ERROR", t.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<Relationship>,
|
||||
response: Response<Relationship>
|
||||
) {
|
||||
if (response.code() == 200) {
|
||||
setOnClickFollow(account)
|
||||
} else if (response.code() == 401) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.access_token_invalid),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenResumed {
|
||||
try {
|
||||
pixelfedAPI.unfollow(account.id.orEmpty(), "Bearer $accessToken")
|
||||
setOnClickFollow(account)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("FOLLOW ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ package com.h.pixeldroid.utils.api
|
||||
import com.h.pixeldroid.utils.api.objects.*
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.Deferred
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
@ -35,17 +36,17 @@ interface PixelfedAPI {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/apps")
|
||||
fun registerApplication(
|
||||
suspend fun registerApplication(
|
||||
@Field("client_name") client_name: String,
|
||||
@Field("redirect_uris") redirect_uris: String,
|
||||
@Field("scopes") scopes: String? = null,
|
||||
@Field("website") website: String? = null
|
||||
): Single<Application>
|
||||
): Application
|
||||
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/oauth/token")
|
||||
fun obtainToken(
|
||||
suspend fun obtainToken(
|
||||
@Field("client_id") client_id: String,
|
||||
@Field("client_secret") client_secret: String,
|
||||
@Field("redirect_uri") redirect_uri: String? = null,
|
||||
@ -53,41 +54,41 @@ interface PixelfedAPI {
|
||||
@Field("code") code: String? = null,
|
||||
@Field("grant_type") grant_type: String? = null,
|
||||
@Field("refresh_token") refresh_token: String? = null
|
||||
): Single<Token>
|
||||
): Token
|
||||
|
||||
// get instance configuration
|
||||
@GET("/api/v1/instance")
|
||||
fun instance() : Single<Instance>
|
||||
suspend fun instance() : Instance
|
||||
|
||||
/**
|
||||
* Instance info from the Nodeinfo .well_known (https://nodeinfo.diaspora.software/protocol.html) endpoint
|
||||
*/
|
||||
@GET("/.well-known/nodeinfo")
|
||||
fun wellKnownNodeInfo() : Single<NodeInfoJRD>
|
||||
suspend fun wellKnownNodeInfo() : NodeInfoJRD
|
||||
|
||||
/**
|
||||
* Instance info from [NodeInfo] (https://nodeinfo.diaspora.software/schema.html) endpoint
|
||||
*/
|
||||
@GET
|
||||
fun nodeInfoSchema(
|
||||
suspend fun nodeInfoSchema(
|
||||
@Url nodeInfo_schema_url: String
|
||||
) : Call<NodeInfo>
|
||||
) : NodeInfo
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/accounts/{id}/follow")
|
||||
fun 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
|
||||
) : Call<Relationship>
|
||||
) : Relationship
|
||||
|
||||
@POST("/api/v1/accounts/{id}/unfollow")
|
||||
fun unfollow(
|
||||
suspend fun unfollow(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Path("id") statusId: String,
|
||||
@Header("Authorization") authorization: String
|
||||
) : Call<Relationship>
|
||||
) : Relationship
|
||||
|
||||
@POST("api/v1/statuses/{id}/favourite")
|
||||
fun likePost(
|
||||
@ -205,10 +206,11 @@ interface PixelfedAPI {
|
||||
): List<Notification>
|
||||
|
||||
@GET("/api/v1/accounts/verify_credentials")
|
||||
fun verifyCredentials(
|
||||
suspend fun verifyCredentials(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String
|
||||
): Call<Account>
|
||||
): Account
|
||||
|
||||
|
||||
@GET("/api/v1/accounts/{id}/statuses")
|
||||
fun accountPosts(
|
||||
@ -217,10 +219,10 @@ interface PixelfedAPI {
|
||||
): Call<List<Status>>
|
||||
|
||||
@GET("/api/v1/accounts/relationships")
|
||||
fun checkRelationships(
|
||||
suspend fun checkRelationships(
|
||||
@Header("Authorization") authorization : String,
|
||||
@Query("id[]") account_ids : List<String>
|
||||
) : Call<List<Relationship>>
|
||||
) : List<Relationship>
|
||||
|
||||
@GET("/api/v1/accounts/{id}/followers")
|
||||
suspend fun followers(
|
||||
|
@ -1,9 +1,6 @@
|
||||
package com.h.pixeldroid.utils.db.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.*
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
|
||||
@Dao
|
||||
@ -11,6 +8,9 @@ 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("SELECT * FROM users")
|
||||
fun getAll(): List<UserDatabaseEntity>
|
||||
|
||||
|
@ -2,9 +2,11 @@ package com.h.pixeldroid.utils.di
|
||||
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.db.addUser
|
||||
import com.h.pixeldroid.utils.db.entities.UserDatabaseEntity
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.*
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
@ -18,11 +20,11 @@ class APIModule{
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAPIHolder(db: AppDatabase): PixelfedAPIHolder {
|
||||
return PixelfedAPIHolder(db.userDao().getActiveUser())
|
||||
return PixelfedAPIHolder(db)
|
||||
}
|
||||
}
|
||||
|
||||
class TokenAuthenticator(val user: UserDatabaseEntity) : Authenticator {
|
||||
class TokenAuthenticator(val user: UserDatabaseEntity, val db: AppDatabase) : Authenticator {
|
||||
|
||||
private val pixelfedAPI = PixelfedAPI.createFromUrl(user.instance_uri)
|
||||
|
||||
@ -32,37 +34,48 @@ class TokenAuthenticator(val user: UserDatabaseEntity) : Authenticator {
|
||||
return null // Give up, we've already failed to authenticate.
|
||||
}
|
||||
// Refresh the access_token using a synchronous api request
|
||||
val newAccessToken: String = try {
|
||||
pixelfedAPI.obtainToken(
|
||||
scope = "", grant_type = "refresh_token",
|
||||
refresh_token = user.refreshToken, client_id = user.clientId, client_secret = user.clientSecret
|
||||
).blockingGet().access_token
|
||||
val newAccessToken: String? = try {
|
||||
runBlocking {
|
||||
pixelfedAPI.obtainToken(
|
||||
scope = "",
|
||||
grant_type = "refresh_token",
|
||||
refresh_token = user.refreshToken,
|
||||
client_id = user.clientId,
|
||||
client_secret = user.clientSecret
|
||||
).access_token
|
||||
}
|
||||
}catch (e: Exception){
|
||||
null
|
||||
}.orEmpty()
|
||||
}
|
||||
|
||||
if (newAccessToken != null) {
|
||||
db.userDao().updateAccessToken(newAccessToken, user.user_id, user.instance_uri)
|
||||
}
|
||||
|
||||
// Add new header to rejected request and retry it
|
||||
return response.request.newBuilder()
|
||||
.header("Authorization", "Bearer $newAccessToken")
|
||||
.header("Authorization", "Bearer ${newAccessToken.orEmpty()}")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
class PixelfedAPIHolder(user: UserDatabaseEntity?){
|
||||
class PixelfedAPIHolder(db: AppDatabase?){
|
||||
private val intermediate: Retrofit.Builder = Retrofit.Builder()
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
var api: PixelfedAPI? = if (user != null) setDomain(user) else null
|
||||
var api: PixelfedAPI? =
|
||||
db?.userDao()?.getActiveUser()?.let {
|
||||
setDomainToCurrentUser(db, it)
|
||||
}
|
||||
|
||||
fun setDomainToCurrentUser(db: AppDatabase): PixelfedAPI {
|
||||
return setDomain(db.userDao().getActiveUser()!!)
|
||||
}
|
||||
|
||||
fun setDomain(user: UserDatabaseEntity): PixelfedAPI {
|
||||
fun setDomainToCurrentUser(
|
||||
db: AppDatabase,
|
||||
user: UserDatabaseEntity = db.userDao().getActiveUser()!!
|
||||
): PixelfedAPI {
|
||||
val newAPI = intermediate
|
||||
.baseUrl(user.instance_uri)
|
||||
.client(OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user)).build())
|
||||
.build().create(PixelfedAPI::class.java)
|
||||
.baseUrl(user.instance_uri)
|
||||
.client(OkHttpClient().newBuilder().authenticator(TokenAuthenticator(user, db)).build())
|
||||
.build().create(PixelfedAPI::class.java)
|
||||
api = newAPI
|
||||
return newAPI
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
<string name="registration_failed">"Could not register the application with this server"</string>
|
||||
<string name="browser_launch_failed">"Could not launch a browser, do you have one?"</string>
|
||||
<string name="auth_failed">"Could not authenticate"</string>
|
||||
<string name="verify_credentials">"Could not get user information"</string>
|
||||
<string name="token_error">"Error getting token"</string>
|
||||
<string name="instance_error">"Could not get instance information"</string>
|
||||
<string name="instance_not_pixelfed_warning">"This doesn't seem to be a Pixelfed instance, so the app could break in unexpected ways."</string>
|
||||
|
@ -112,10 +112,11 @@ class APIUnitTest {
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody(""" {"id":3197,"name":"Pixeldroid","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":3197,"client_secret":"hhRwLupqUJPghKsZzpZtxNV67g5DBdPYCqW6XE3m","vapid_key":null}"""
|
||||
)))
|
||||
val call: Single<Application> = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow")
|
||||
val application: Application = runBlocking {
|
||||
PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.registerApplication("Pixeldroid", "urn:ietf:wg:oauth:2.0:oob", "read write follow")
|
||||
}
|
||||
|
||||
val application: Application = call.toFuture().get()
|
||||
assertEquals("3197", application.client_id)
|
||||
assertEquals("hhRwLupqUJPghKsZzpZtxNV67g5DBdPYCqW6XE3m", application.client_secret)
|
||||
assertEquals("Pixeldroid", application.name)
|
||||
@ -141,10 +142,14 @@ class APIUnitTest {
|
||||
val OAUTH_SCHEME = "oauth2redirect"
|
||||
val SCOPE = "read write follow"
|
||||
val PACKAGE_ID = "com.h.pixeldroid"
|
||||
val call: Single<Token> = PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.obtainToken("123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc",
|
||||
"authorization_code")
|
||||
val token: Token = call.toFuture().get()
|
||||
|
||||
val token: Token = runBlocking {
|
||||
PixelfedAPI.createFromUrl("http://localhost:8089")
|
||||
.obtainToken(
|
||||
"123", "ssqdfqsdfqds", "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, "abc",
|
||||
"authorization_code"
|
||||
)
|
||||
}
|
||||
assertEquals("ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0", token.access_token)
|
||||
assertEquals("Bearer", token.token_type)
|
||||
assertEquals("read write follow push", token.scope)
|
||||
|
Loading…
x
Reference in New Issue
Block a user