Merge branch 'remove_callback' into 'master'
Use coroutines instead of callbacks Closes #270 See merge request pixeldroid/PixelDroid!279
This commit is contained in:
commit
32997f5e6c
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.core.net.toFile
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
@ -34,7 +35,9 @@ import kotlinx.android.synthetic.main.image_album_creation.view.*
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
private const val TAG = "Post Creation Activity"
|
||||
private const val MORE_PICTURES_REQUEST_CODE = 0xffff
|
||||
@ -213,33 +216,30 @@ class PostCreationActivity : BaseActivity() {
|
||||
private fun post() {
|
||||
val description = new_post_description_input_field.text.toString()
|
||||
enableButton(false)
|
||||
pixelfedAPI.postStatus(
|
||||
authorization = "Bearer $accessToken",
|
||||
statusText = description,
|
||||
media_ids = muListOfIds.toList()
|
||||
).enqueue(object: Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
pixelfedAPI.postStatus(
|
||||
authorization = "Bearer $accessToken",
|
||||
statusText = description,
|
||||
media_ids = muListOfIds.toList()
|
||||
)
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
} catch (exception: IOException) {
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, exception.toString())
|
||||
enableButton(true)
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, t.message + call.request())
|
||||
Log.e(TAG, exception.response().toString() + exception.message().toString())
|
||||
enableButton(true)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
if (response.code() == 200) {
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
val intent = Intent(this@PostCreationActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, call.request().toString() + response.raw().toString())
|
||||
enableButton(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableButton(enable: Boolean = true){
|
||||
|
@ -12,10 +12,13 @@ import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.text.toSpanned
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.api.objects.Account.Companion.getAccountFromId
|
||||
import com.h.pixeldroid.utils.api.objects.Account.Companion.openAccountFromId
|
||||
import com.h.pixeldroid.utils.api.objects.Mention
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.text.ParseException
|
||||
@ -51,7 +54,8 @@ fun parseHTMLText(
|
||||
mentions: List<Mention>?,
|
||||
api : PixelfedAPI,
|
||||
context: Context,
|
||||
credential: String
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) : Spanned {
|
||||
//Convert text to spannable
|
||||
val content = fromHtml(text)
|
||||
@ -103,7 +107,9 @@ fun parseHTMLText(
|
||||
override fun onClick(widget: View) {
|
||||
Log.e("MENTION", "CLICKED")
|
||||
//Retrieve the account for the given profile
|
||||
getAccountFromId(accountId, api, context, credential)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
openAccountFromId(accountId, api, context, credential)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -3,6 +3,7 @@ package com.h.pixeldroid.posts
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.api.objects.Report
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
@ -10,7 +11,9 @@ import com.h.pixeldroid.utils.BaseActivity
|
||||
import kotlinx.android.synthetic.main.activity_report.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
class ReportActivity : BaseActivity() {
|
||||
|
||||
@ -37,33 +40,34 @@ class ReportActivity : BaseActivity() {
|
||||
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
val api = apiHolder.api ?: apiHolder.setDomainToCurrentUser(db)
|
||||
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), textInputLayout.editText?.text.toString())
|
||||
.enqueue(object : Callback<Report> {
|
||||
override fun onResponse(
|
||||
call: Call<Report>,
|
||||
response: Response<Report>
|
||||
) {
|
||||
if (response.body() == null || !response.isSuccessful) {
|
||||
textInputLayout.error = getString(R.string.report_error)
|
||||
reportButton.visibility = View.VISIBLE
|
||||
textInputLayout.editText?.isEnabled = true
|
||||
reportProgressBar.visibility = View.GONE
|
||||
} else {
|
||||
reportProgressBar.visibility = View.GONE
|
||||
reportButton.isEnabled = false
|
||||
reportButton.text = getString(R.string.reported)
|
||||
reportButton.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Report>, t: Throwable) {
|
||||
Log.e("REPORT:", t.toString())
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
api.report("Bearer $accessToken", status?.account?.id!!, listOf(status), textInputLayout.editText?.text.toString())
|
||||
|
||||
reportStatus(true)
|
||||
} catch (exception: IOException) {
|
||||
reportStatus(false)
|
||||
} catch (exception: HttpException) {
|
||||
reportStatus(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportStatus(success: Boolean){
|
||||
if(success){
|
||||
reportProgressBar.visibility = View.GONE
|
||||
reportButton.isEnabled = false
|
||||
reportButton.text = getString(R.string.reported)
|
||||
reportButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
textInputLayout.error = getString(R.string.report_error)
|
||||
reportButton.visibility = View.VISIBLE
|
||||
textInputLayout.editText?.isEnabled = true
|
||||
reportProgressBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
onBackPressed()
|
||||
|
@ -23,12 +23,12 @@ import com.bumptech.glide.RequestBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.h.pixeldroid.utils.api.objects.Attachment
|
||||
import com.h.pixeldroid.utils.api.objects.Context
|
||||
import com.h.pixeldroid.utils.api.objects.Status
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.db.AppDatabase
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse
|
||||
@ -206,12 +206,24 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
}.attach()
|
||||
}
|
||||
|
||||
private fun setDescription(rootView: View, api: PixelfedAPI, credential: String) {
|
||||
private fun setDescription(
|
||||
rootView: View,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
rootView.findViewById<TextView>(R.id.description).apply {
|
||||
if (status?.content.isNullOrBlank()) {
|
||||
visibility = View.GONE
|
||||
} else {
|
||||
text = parseHTMLText(status?.content.orEmpty(), status?.mentions, api, rootView.context, credential)
|
||||
text = parseHTMLText(
|
||||
status?.content.orEmpty(),
|
||||
status?.mentions,
|
||||
api,
|
||||
rootView.context,
|
||||
credential,
|
||||
lifecycleScope
|
||||
)
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
@ -222,32 +234,32 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
val credential = "Bearer ${user.accessToken}"
|
||||
//Set the special HTML text
|
||||
setDescription(holder.view, api, credential)
|
||||
setDescription(holder.view, api, credential, lifecycleScope)
|
||||
|
||||
//Activate onclickListeners
|
||||
activateLiker(
|
||||
holder, api, credential,
|
||||
status?.favourited ?: false
|
||||
status?.favourited ?: false,
|
||||
lifecycleScope
|
||||
)
|
||||
activateReblogger(
|
||||
holder, api, credential,
|
||||
status?.reblogged ?: false
|
||||
status?.reblogged ?: false,
|
||||
lifecycleScope
|
||||
)
|
||||
activateCommenter(holder, api, credential)
|
||||
activateCommenter(holder, api, credential, lifecycleScope)
|
||||
|
||||
showComments(holder, api, credential)
|
||||
|
||||
//Activate double tap liking
|
||||
activateDoubleTapLiker(holder, api, credential)
|
||||
showComments(holder, api, credential, lifecycleScope)
|
||||
|
||||
activateMoreButton(holder, api, db, lifecycleScope)
|
||||
}
|
||||
|
||||
private fun activateReblogger(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
isReblogged: Boolean
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
isReblogged: Boolean,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
holder.reblogger.apply {
|
||||
//Set initial button state
|
||||
@ -255,12 +267,14 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
//Activate the button
|
||||
setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
// Button is active
|
||||
undoReblogPost(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive
|
||||
reblogPost(holder, api, credential)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
if (buttonState) {
|
||||
// Button is active
|
||||
undoReblogPost(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive
|
||||
reblogPost(holder, api, credential)
|
||||
}
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
@ -268,63 +282,50 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun reblogPost(
|
||||
private suspend fun reblogPost(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String
|
||||
) {
|
||||
//Call the api function
|
||||
status?.id?.let {
|
||||
api.reblogStatus(credential, it).enqueue(object : Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Log.e("REBLOG ERROR", t.toString())
|
||||
holder.reblogger.isChecked = false
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
if(response.code() == 200) {
|
||||
val resp = response.body()!!
|
||||
try {
|
||||
val resp = api.reblogStatus(credential, it)
|
||||
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares(holder.view.context)
|
||||
holder.reblogger.isChecked = resp.reblogged!!
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
holder.reblogger.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares(holder.view.context)
|
||||
holder.reblogger.isChecked = resp.reblogged!!
|
||||
} catch (exception: IOException) {
|
||||
Log.e("REBLOG ERROR", exception.toString())
|
||||
holder.reblogger.isChecked = false
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.reblogger.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun undoReblogPost(
|
||||
private suspend fun undoReblogPost(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
//Call the api function
|
||||
status?.id?.let {
|
||||
api.undoReblogStatus(credential, it).enqueue(object : Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Log.e("REBLOG ERROR", t.toString())
|
||||
holder.reblogger.isChecked = true
|
||||
}
|
||||
try {
|
||||
val resp = api.undoReblogStatus(credential, it)
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
if(response.code() == 200) {
|
||||
val resp = response.body()!!
|
||||
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares(holder.view.context)
|
||||
holder.reblogger.isChecked = resp.reblogged!!
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
holder.reblogger.isChecked = true
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares(holder.view.context)
|
||||
holder.reblogger.isChecked = resp.reblogged!!
|
||||
} catch (exception: IOException) {
|
||||
Log.e("REBLOG ERROR", exception.toString())
|
||||
holder.reblogger.isChecked = true
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.reblogger.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -444,45 +445,12 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun activateDoubleTapLiker(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String
|
||||
) {
|
||||
holder.apply {
|
||||
var clicked = false
|
||||
postPic.setOnClickListener {
|
||||
//Check that the post isn't hidden
|
||||
if(sensitiveW.visibility == View.GONE) {
|
||||
//Check for double click
|
||||
if(clicked) {
|
||||
if (holder.liker.isChecked) {
|
||||
// Button is active, unlike
|
||||
holder.liker.isChecked = false
|
||||
unLikePostCall(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive, like
|
||||
holder.liker.playAnimation()
|
||||
holder.liker.isChecked = true
|
||||
likePostCall(holder, api, credential)
|
||||
}
|
||||
} else {
|
||||
clicked = true
|
||||
|
||||
//Reset clicked to false after 500ms
|
||||
postPic.handler.postDelayed(fun() { clicked = false }, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun activateLiker(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
isLiked: Boolean
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
isLiked: Boolean,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
|
||||
holder.liker.apply {
|
||||
@ -491,84 +459,104 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
//Activate the liker
|
||||
setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
// Button is active, unlike
|
||||
unLikePostCall(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive, like
|
||||
likePostCall(holder, api, credential)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
if (buttonState) {
|
||||
// Button is active, unlike
|
||||
unLikePostCall(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive, like
|
||||
likePostCall(holder, api, credential)
|
||||
}
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun likePostCall(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
//Call the api function
|
||||
status?.id?.let {
|
||||
api.likePost(credential, it).enqueue(object : Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Log.e("LIKE ERROR", t.toString())
|
||||
holder.liker.isChecked = false
|
||||
}
|
||||
//Activate double tap liking
|
||||
holder.apply {
|
||||
var clicked = false
|
||||
postPic.setOnClickListener {
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Check that the post isn't hidden
|
||||
if(sensitiveW.visibility == View.GONE) {
|
||||
//Check for double click
|
||||
if(clicked) {
|
||||
if (holder.liker.isChecked) {
|
||||
// Button is active, unlike
|
||||
holder.liker.isChecked = false
|
||||
unLikePostCall(holder, api, credential)
|
||||
} else {
|
||||
// Button is inactive, like
|
||||
holder.liker.playAnimation()
|
||||
holder.liker.isChecked = true
|
||||
likePostCall(holder, api, credential)
|
||||
}
|
||||
} else {
|
||||
clicked = true
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
if(response.code() == 200) {
|
||||
val resp = response.body()!!
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes(holder.view.context)
|
||||
holder.liker.isChecked = resp.favourited ?: false
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
holder.liker.isChecked = false
|
||||
//Reset clicked to false after 500ms
|
||||
postPic.handler.postDelayed(fun() { clicked = false }, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unLikePostCall(
|
||||
private suspend fun likePostCall(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
//Call the api function
|
||||
status?.id?.let {
|
||||
api.unlikePost(credential, it).enqueue(object : Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Log.e("UNLIKE ERROR", t.toString())
|
||||
holder.liker.isChecked = true
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
if(response.code() == 200) {
|
||||
val resp = response.body()!!
|
||||
try {
|
||||
val resp = api.likePost(credential, it)
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes(holder.view.context)
|
||||
holder.liker.isChecked = resp.favourited ?: false
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
holder.liker.isChecked = true
|
||||
}
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes(holder.view.context)
|
||||
holder.liker.isChecked = resp.favourited ?: false
|
||||
} catch (exception: IOException) {
|
||||
Log.e("LIKE ERROR", exception.toString())
|
||||
holder.liker.isChecked = false
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.liker.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private suspend fun unLikePostCall(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
//Call the api function
|
||||
status?.id?.let {
|
||||
|
||||
})
|
||||
try {
|
||||
val resp = api.unlikePost(credential, it)
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes(holder.view.context)
|
||||
holder.liker.isChecked = resp.favourited ?: false
|
||||
} catch (exception: IOException) {
|
||||
Log.e("UNLIKE ERROR", exception.toString())
|
||||
holder.liker.isChecked = true
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("RESPONSE_CODE", exception.code().toString())
|
||||
holder.liker.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showComments(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
//Show all comments of a post
|
||||
if (status?.replies_count == 0) {
|
||||
@ -580,8 +568,10 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
setOnClickListener {
|
||||
visibility = View.GONE
|
||||
|
||||
//Retrieve the comments
|
||||
retrieveComments(holder, api, credential)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
//Retrieve the comments
|
||||
retrieveComments(holder, api, credential)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -590,7 +580,8 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
private fun activateCommenter(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String
|
||||
credential: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
//Toggle comment button
|
||||
toggleCommentInput(holder)
|
||||
@ -606,9 +597,10 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} else {
|
||||
|
||||
//Post the comment
|
||||
postComment(holder, api, credential)
|
||||
lifecycleScope.launchWhenCreated {
|
||||
postComment(holder, api, credential)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -648,43 +640,34 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
view.commentText.text = commentContent
|
||||
}
|
||||
|
||||
private fun retrieveComments(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
private suspend fun retrieveComments(
|
||||
holder: StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
) {
|
||||
status?.id?.let {
|
||||
api.statusComments(it, credential).enqueue(object :
|
||||
Callback<Context> {
|
||||
override fun onFailure(call: Call<Context>, t: Throwable) {
|
||||
Log.e("COMMENT FETCH ERROR", t.toString())
|
||||
try {
|
||||
val statuses = api.statusComments(it, credential).descendants
|
||||
|
||||
holder.commentCont.removeAllViews()
|
||||
|
||||
//Create the new views for each comment
|
||||
for (status in statuses) {
|
||||
addComment(holder.view.context, holder.commentCont, status.account!!.username!!,
|
||||
status.content!!
|
||||
)
|
||||
}
|
||||
holder.commentCont.visibility = View.VISIBLE
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<Context>,
|
||||
response: Response<Context>
|
||||
) {
|
||||
if(response.code() == 200) {
|
||||
val statuses = response.body()!!.descendants
|
||||
|
||||
holder.commentCont.removeAllViews()
|
||||
|
||||
//Create the new views for each comment
|
||||
for (status in statuses) {
|
||||
addComment(holder.view.context, holder.commentCont, status.account!!.username!!,
|
||||
status.content!!
|
||||
)
|
||||
}
|
||||
holder.commentCont.visibility = View.VISIBLE
|
||||
} else {
|
||||
Log.e("COMMENT ERROR", "${response.code()} with body ${response.errorBody()}")
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT FETCH ERROR", exception.toString())
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("COMMENT ERROR", "${exception.code()} with body ${exception.response()?.errorBody()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun postComment(
|
||||
private suspend fun postComment(
|
||||
holder : StatusViewHolder,
|
||||
api: PixelfedAPI,
|
||||
credential: String,
|
||||
@ -692,39 +675,34 @@ class StatusViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
val textIn = holder.comment.text
|
||||
val nonNullText = textIn.toString()
|
||||
status?.id?.let {
|
||||
api.postStatus(credential, nonNullText, it).enqueue(object :
|
||||
Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Log.e("COMMENT ERROR", t.toString())
|
||||
Toast.makeText(
|
||||
holder.view.context, holder.view.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
try {
|
||||
val response = api.postStatus(credential, nonNullText, it)
|
||||
holder.commentIn.visibility = View.GONE
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
//Check that the received response code is valid
|
||||
if (response.code() == 200) {
|
||||
val resp = response.body()!!
|
||||
holder.commentIn.visibility = View.GONE
|
||||
//Add the comment to the comment section
|
||||
addComment(
|
||||
holder.view.context, holder.commentCont, response.account!!.username!!,
|
||||
response.content!!
|
||||
)
|
||||
|
||||
//Add the comment to the comment section
|
||||
addComment(
|
||||
holder.view.context, holder.commentCont, resp.account!!.username!!,
|
||||
resp.content!!
|
||||
)
|
||||
|
||||
Toast.makeText(
|
||||
holder.view.context,
|
||||
holder.view.context.getString(R.string.comment_posted).format(textIn),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Log.e("COMMENT SUCCESS", "posted: $textIn")
|
||||
} else {
|
||||
Log.e("ERROR_CODE", response.code().toString())
|
||||
}
|
||||
}
|
||||
})
|
||||
Toast.makeText(
|
||||
holder.view.context,
|
||||
holder.view.context.getString(R.string.comment_posted).format(textIn),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: IOException) {
|
||||
Log.e("COMMENT ERROR", exception.toString())
|
||||
Toast.makeText(
|
||||
holder.view.context, holder.view.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
} catch (exception: HttpException) {
|
||||
Toast.makeText(
|
||||
holder.view.context, holder.view.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Log.e("ERROR_CODE", exception.code().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,9 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.ExperimentalPagingApi
|
||||
import androidx.paging.PagingDataAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
@ -53,7 +55,10 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
|
||||
|
||||
// get the view model
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
viewModel = ViewModelProvider(this, ViewModelFactory(db, db.notificationDao(), NotificationsRemoteMediator(apiHolder, db)))
|
||||
viewModel = ViewModelProvider(
|
||||
this,
|
||||
ViewModelFactory(db, db.notificationDao(), NotificationsRemoteMediator(apiHolder, db))
|
||||
)
|
||||
.get(FeedViewModel::class.java) as FeedViewModel<Notification>
|
||||
|
||||
launch()
|
||||
@ -62,149 +67,201 @@ class NotificationsFragment : CachedFeedFragment<Notification>() {
|
||||
return view
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* View Holder for a [Notification] RecyclerView list item.
|
||||
*/
|
||||
class NotificationViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val notificationType: TextView = view.notification_type
|
||||
private val notificationTime: TextView = view.notification_time
|
||||
private val postDescription: TextView = view.notification_post_description
|
||||
private val avatar: ImageView = view.notification_avatar
|
||||
private val photoThumbnail: ImageView = view.notification_photo_thumbnail
|
||||
/**
|
||||
* View Holder for a [Notification] RecyclerView list item.
|
||||
*/
|
||||
class NotificationViewHolder(view: View) : RecyclerView.ViewHolder(view) {
|
||||
private val notificationType: TextView = view.notification_type
|
||||
private val notificationTime: TextView = view.notification_time
|
||||
private val postDescription: TextView = view.notification_post_description
|
||||
private val avatar: ImageView = view.notification_avatar
|
||||
private val photoThumbnail: ImageView = view.notification_photo_thumbnail
|
||||
|
||||
private var notification: Notification? = null
|
||||
private var notification: Notification? = null
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
notification?.openActivity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Notification.openActivity() {
|
||||
val intent: Intent =
|
||||
when (type){
|
||||
Notification.NotificationType.mention, Notification.NotificationType.favourite,
|
||||
Notification.NotificationType.poll, Notification.NotificationType.reblog -> {
|
||||
openPostFromNotification()
|
||||
private fun Notification.openActivity() {
|
||||
val intent: Intent =
|
||||
when (type) {
|
||||
Notification.NotificationType.mention, Notification.NotificationType.favourite,
|
||||
Notification.NotificationType.poll, Notification.NotificationType.reblog -> {
|
||||
openPostFromNotification()
|
||||
}
|
||||
Notification.NotificationType.follow -> {
|
||||
Intent(itemView.context, ProfileActivity::class.java).apply {
|
||||
putExtra(Account.ACCOUNT_TAG, account)
|
||||
}
|
||||
}
|
||||
null -> return //TODO show an error here?
|
||||
}
|
||||
itemView.context.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun Notification.openPostFromNotification(): Intent =
|
||||
Intent(itemView.context, PostActivity::class.java).apply {
|
||||
putExtra(Status.POST_TAG, status)
|
||||
}
|
||||
Notification.NotificationType.follow -> {
|
||||
Intent(itemView.context, ProfileActivity::class.java).apply {
|
||||
putExtra(Account.ACCOUNT_TAG, account)
|
||||
|
||||
|
||||
private fun setNotificationType(
|
||||
type: Notification.NotificationType,
|
||||
username: String,
|
||||
textView: TextView
|
||||
) {
|
||||
val context = textView.context
|
||||
val (format: String, drawable: Drawable?) = when (type) {
|
||||
Notification.NotificationType.follow -> {
|
||||
getStringAndDrawable(
|
||||
context,
|
||||
R.string.followed_notification,
|
||||
R.drawable.ic_follow
|
||||
)
|
||||
}
|
||||
Notification.NotificationType.mention -> {
|
||||
getStringAndDrawable(
|
||||
context,
|
||||
R.string.mention_notification,
|
||||
R.drawable.mention_at_24dp
|
||||
)
|
||||
}
|
||||
|
||||
Notification.NotificationType.reblog -> {
|
||||
getStringAndDrawable(
|
||||
context,
|
||||
R.string.shared_notification,
|
||||
R.drawable.ic_reblog_blue
|
||||
)
|
||||
}
|
||||
|
||||
Notification.NotificationType.favourite -> {
|
||||
getStringAndDrawable(
|
||||
context,
|
||||
R.string.liked_notification,
|
||||
R.drawable.ic_like_full
|
||||
)
|
||||
}
|
||||
Notification.NotificationType.poll -> {
|
||||
getStringAndDrawable(context, R.string.poll_notification, R.drawable.poll)
|
||||
}
|
||||
}
|
||||
null -> return //TODO show an error here?
|
||||
}
|
||||
itemView.context.startActivity(intent)
|
||||
}
|
||||
|
||||
private fun Notification.openPostFromNotification(): Intent =
|
||||
Intent(itemView.context, PostActivity::class.java).apply {
|
||||
putExtra(Status.POST_TAG, status)
|
||||
}
|
||||
|
||||
|
||||
private fun setNotificationType(type: Notification.NotificationType,
|
||||
username: String,
|
||||
textView: TextView
|
||||
){
|
||||
val context = textView.context
|
||||
val (format: String, drawable: Drawable?) = when(type) {
|
||||
Notification.NotificationType.follow -> {
|
||||
getStringAndDrawable(context, R.string.followed_notification, R.drawable.ic_follow)
|
||||
}
|
||||
Notification.NotificationType.mention -> {
|
||||
getStringAndDrawable(context, R.string.mention_notification, R.drawable.mention_at_24dp)
|
||||
}
|
||||
|
||||
Notification.NotificationType.reblog -> {
|
||||
getStringAndDrawable(context, R.string.shared_notification, R.drawable.ic_reblog_blue)
|
||||
}
|
||||
|
||||
Notification.NotificationType.favourite -> {
|
||||
getStringAndDrawable(context, R.string.liked_notification, R.drawable.ic_like_full)
|
||||
}
|
||||
Notification.NotificationType.poll -> {
|
||||
getStringAndDrawable(context, R.string.poll_notification, R.drawable.poll)
|
||||
}
|
||||
}
|
||||
textView.text = format.format(username)
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
drawable,null,null,null
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStringAndDrawable(context: Context, stringToFormat: Int, drawable: Int): Pair<String, Drawable?>
|
||||
= Pair(context.getString(stringToFormat), ContextCompat.getDrawable(context, drawable))
|
||||
|
||||
|
||||
|
||||
fun bind(notification: Notification?, api: PixelfedAPI, accessToken: String) {
|
||||
|
||||
this.notification = notification
|
||||
|
||||
Glide.with(itemView).load(notification?.account?.avatar_static).circleCrop().into(avatar)
|
||||
|
||||
val previewUrl = notification?.status?.media_attachments?.getOrNull(0)?.preview_url
|
||||
if(!previewUrl.isNullOrBlank()){
|
||||
Glide.with(itemView).load(previewUrl)
|
||||
.placeholder(R.drawable.ic_picture_fallback).into(photoThumbnail)
|
||||
} else{
|
||||
photoThumbnail.visibility = View.GONE
|
||||
}
|
||||
|
||||
notification?.type?.let { notification.account?.username?.let { username -> setNotificationType(it, username, notificationType) } }
|
||||
notification?.created_at?.let { setTextViewFromISO8601(it, notificationTime, false, itemView.context) }
|
||||
|
||||
//Convert HTML to clickable text
|
||||
postDescription.text =
|
||||
parseHTMLText(
|
||||
notification?.status?.content ?: "",
|
||||
notification?.status?.mentions,
|
||||
api,
|
||||
itemView.context,
|
||||
"Bearer $accessToken"
|
||||
textView.text = format.format(username)
|
||||
textView.setCompoundDrawablesWithIntrinsicBounds(
|
||||
drawable, null, null, null
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): NotificationViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.fragment_notifications, parent, false)
|
||||
return NotificationViewHolder(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getStringAndDrawable(
|
||||
context: Context,
|
||||
stringToFormat: Int,
|
||||
drawable: Int
|
||||
): Pair<String, Drawable?> =
|
||||
Pair(context.getString(stringToFormat), ContextCompat.getDrawable(context, drawable))
|
||||
|
||||
|
||||
class NotificationsAdapter(private val apiHolder: PixelfedAPIHolder, private val db: AppDatabase) : PagingDataAdapter<Notification, RecyclerView.ViewHolder>(
|
||||
UIMODEL_COMPARATOR
|
||||
) {
|
||||
fun bind(
|
||||
notification: Notification?,
|
||||
api: PixelfedAPI,
|
||||
accessToken: String,
|
||||
lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return NotificationViewHolder.create(parent)
|
||||
}
|
||||
this.notification = notification
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return R.layout.fragment_notifications
|
||||
}
|
||||
Glide.with(itemView).load(notification?.account?.avatar_static).circleCrop()
|
||||
.into(avatar)
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position)
|
||||
uiModel.let {
|
||||
(holder as NotificationViewHolder).bind(it, apiHolder.setDomainToCurrentUser(db), db.userDao().getActiveUser()!!.accessToken)
|
||||
val previewUrl = notification?.status?.media_attachments?.getOrNull(0)?.preview_url
|
||||
if (!previewUrl.isNullOrBlank()) {
|
||||
Glide.with(itemView).load(previewUrl)
|
||||
.placeholder(R.drawable.ic_picture_fallback).into(photoThumbnail)
|
||||
} else {
|
||||
photoThumbnail.visibility = View.GONE
|
||||
}
|
||||
|
||||
notification?.type?.let {
|
||||
notification.account?.username?.let { username ->
|
||||
setNotificationType(
|
||||
it,
|
||||
username,
|
||||
notificationType
|
||||
)
|
||||
}
|
||||
}
|
||||
notification?.created_at?.let {
|
||||
setTextViewFromISO8601(
|
||||
it,
|
||||
notificationTime,
|
||||
false,
|
||||
itemView.context
|
||||
)
|
||||
}
|
||||
|
||||
//Convert HTML to clickable text
|
||||
postDescription.text =
|
||||
parseHTMLText(
|
||||
notification?.status?.content ?: "",
|
||||
notification?.status?.mentions,
|
||||
api,
|
||||
itemView.context,
|
||||
"Bearer $accessToken",
|
||||
lifecycleScope
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup): NotificationViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.fragment_notifications, parent, false)
|
||||
return NotificationViewHolder(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val UIMODEL_COMPARATOR = object : DiffUtil.ItemCallback<Notification>() {
|
||||
override fun areItemsTheSame(oldItem: Notification, newItem: Notification): Boolean {
|
||||
|
||||
inner class NotificationsAdapter(
|
||||
private val apiHolder: PixelfedAPIHolder,
|
||||
private val db: AppDatabase
|
||||
) : PagingDataAdapter<Notification, RecyclerView.ViewHolder>(
|
||||
object : DiffUtil.ItemCallback<Notification>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: Notification,
|
||||
newItem: Notification
|
||||
): Boolean {
|
||||
return oldItem.id == newItem.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Notification, newItem: Notification): Boolean =
|
||||
override fun areContentsTheSame(
|
||||
oldItem: Notification,
|
||||
newItem: Notification
|
||||
): Boolean =
|
||||
oldItem == newItem
|
||||
}
|
||||
) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return NotificationViewHolder.create(parent)
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return R.layout.fragment_notifications
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val uiModel = getItem(position)
|
||||
uiModel.let {
|
||||
(holder as NotificationViewHolder).bind(
|
||||
it,
|
||||
apiHolder.setDomainToCurrentUser(db),
|
||||
db.userDao().getActiveUser()!!.accessToken,
|
||||
lifecycleScope
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -109,23 +107,17 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun getAndSetAccount(id: String){
|
||||
pixelfedAPI.getAccount("Bearer $accessToken", id)
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.code() == 200) {
|
||||
val account = response.body()!!
|
||||
|
||||
setContent(account)
|
||||
} else {
|
||||
showError()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("ProfileActivity:", t.toString())
|
||||
showError()
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenCreated {
|
||||
val account = try{
|
||||
pixelfedAPI.getAccount("Bearer $accessToken", id)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("ProfileActivity:", exception.toString())
|
||||
return@launchWhenCreated showError()
|
||||
} catch (exception: HttpException) {
|
||||
return@launchWhenCreated showError()
|
||||
}
|
||||
setContent(account)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showError(@StringRes errorText: Int = R.string.loading_toast, show: Boolean = true){
|
||||
@ -153,7 +145,8 @@ class ProfileActivity : BaseActivity() {
|
||||
val description = findViewById<TextView>(R.id.descriptionTextView)
|
||||
description.text = parseHTMLText(
|
||||
account.note ?: "", emptyList(), pixelfedAPI,
|
||||
applicationContext, "Bearer $accessToken"
|
||||
applicationContext, "Bearer $accessToken",
|
||||
lifecycleScope
|
||||
)
|
||||
|
||||
val accountName = findViewById<TextView>(R.id.accountNameTextView)
|
||||
@ -244,40 +237,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 +274,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 +300,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import android.widget.*
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
@ -32,7 +33,9 @@ import com.mikepenz.iconics.utils.paddingDp
|
||||
import com.mikepenz.iconics.utils.sizeDp
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* This fragment lets you search and use Pixelfed's Discover feature
|
||||
@ -107,25 +110,17 @@ class SearchDiscoverFragment : BaseFragment() {
|
||||
|
||||
private fun getDiscover() {
|
||||
|
||||
api.discover("Bearer $accessToken")
|
||||
.enqueue(object : Callback<DiscoverPosts> {
|
||||
|
||||
override fun onFailure(call: Call<DiscoverPosts>, t: Throwable) {
|
||||
showError()
|
||||
Log.e("SearchDiscoverFragment:", t.toString())
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<DiscoverPosts>, response: Response<DiscoverPosts>) {
|
||||
if(response.code() == 200) {
|
||||
val discoverPosts = response.body()!!
|
||||
adapter.addPosts(discoverPosts.posts)
|
||||
showError(show = false)
|
||||
}
|
||||
else {
|
||||
showError()
|
||||
}
|
||||
}
|
||||
})
|
||||
lifecycleScope.launchWhenCreated {
|
||||
try {
|
||||
val discoverPosts = api.discover("Bearer $accessToken")
|
||||
adapter.addPosts(discoverPosts.posts)
|
||||
showError(show = false)
|
||||
} catch (exception: IOException) {
|
||||
showError()
|
||||
} catch (exception: HttpException) {
|
||||
showError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,66 +54,61 @@ 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(
|
||||
suspend fun likePost(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") statusId: String
|
||||
|
||||
) : Call<Status>
|
||||
) : Status
|
||||
|
||||
@POST("/api/v1/statuses/{id}/unfavourite")
|
||||
fun unlikePost(
|
||||
suspend fun unlikePost(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") statusId: String
|
||||
) : Call<Status>
|
||||
|
||||
@GET("/api/v1/statuses/{id}/favourited_by")
|
||||
fun postLikedBy(
|
||||
@Path("id") statusId: String
|
||||
) : Call<List<Account>>
|
||||
) : Status
|
||||
|
||||
//Used in our case to post a comment or a status
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/statuses")
|
||||
fun postStatus(
|
||||
suspend fun postStatus(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String,
|
||||
@Field("status") statusText : String,
|
||||
@ -127,7 +123,7 @@ interface PixelfedAPI {
|
||||
@Field("visibility") visibility : String = "public",
|
||||
@Field("scheduled_at") scheduled_at : String? = null,
|
||||
@Field("language") language : String? = null
|
||||
) : Call<Status>
|
||||
) : Status
|
||||
|
||||
@DELETE("/api/v1/statuses/{id}")
|
||||
suspend fun deleteStatus(
|
||||
@ -137,24 +133,24 @@ interface PixelfedAPI {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/statuses/{id}/reblog")
|
||||
fun reblogStatus(
|
||||
suspend fun reblogStatus(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") statusId: String,
|
||||
@Field("visibility") visibility: String? = null
|
||||
) : Call<Status>
|
||||
) : Status
|
||||
|
||||
@POST("/api/v1/statuses/{id}/unreblog")
|
||||
fun undoReblogStatus(
|
||||
suspend fun undoReblogStatus(
|
||||
@Path("id") statusId: String,
|
||||
@Header("Authorization") authorization: String
|
||||
) : Call<Status>
|
||||
) : Status
|
||||
|
||||
//Used in our case to retrieve comments for a given status
|
||||
@GET("/api/v1/statuses/{id}/context")
|
||||
fun statusComments(
|
||||
suspend fun statusComments(
|
||||
@Path("id") statusId: String,
|
||||
@Header("Authorization") authorization: String? = null
|
||||
) : Call<Context>
|
||||
) : Context
|
||||
|
||||
@GET("/api/v1/timelines/public")
|
||||
suspend fun timelinePublic(
|
||||
@ -205,10 +201,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 +214,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(
|
||||
@ -243,10 +240,10 @@ interface PixelfedAPI {
|
||||
) : Response<List<Account>>
|
||||
|
||||
@GET("/api/v1/accounts/{id}")
|
||||
fun getAccount(
|
||||
suspend fun getAccount(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") accountId : String
|
||||
): Call<Account>
|
||||
): Account
|
||||
|
||||
@GET("/api/v1/statuses/{id}")
|
||||
suspend fun getStatus(
|
||||
@ -264,19 +261,19 @@ interface PixelfedAPI {
|
||||
|
||||
// get discover
|
||||
@GET("/api/v2/discover/posts")
|
||||
fun discover(
|
||||
suspend fun discover(
|
||||
@Header("Authorization") authorization: String
|
||||
) : Call<DiscoverPosts>
|
||||
) : DiscoverPosts
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/reports")
|
||||
@JvmSuppressWildcards
|
||||
fun report(
|
||||
suspend fun report(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Field("account_id") account_id: String,
|
||||
@Field("status_ids") status_ids: List<Status>,
|
||||
@Field("comment") comment: String,
|
||||
@Field("forward") forward: Boolean = true
|
||||
) : Call<Report>
|
||||
) : Report
|
||||
|
||||
}
|
@ -6,9 +6,14 @@ import android.util.Log
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import com.h.pixeldroid.profile.ProfileActivity
|
||||
import com.h.pixeldroid.utils.api.PixelfedAPI
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
|
||||
/*
|
||||
@ -52,28 +57,19 @@ data class Account(
|
||||
/**
|
||||
* @brief Opens an activity of the profile with the given id
|
||||
*/
|
||||
fun getAccountFromId(id: String, api : PixelfedAPI, context: Context, credential: String) {
|
||||
Log.e("ACCOUNT_ID", id)
|
||||
api.getAccount(credential, id).enqueue( object : Callback<Account> {
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("GET ACCOUNT ERROR", t.toString())
|
||||
suspend fun openAccountFromId(id: String, api : PixelfedAPI, context: Context, credential: String) {
|
||||
val account = try {
|
||||
api.getAccount(credential, id)
|
||||
} catch (exception: IOException) {
|
||||
Log.e("GET ACCOUNT ERROR", exception.toString())
|
||||
return
|
||||
} catch (exception: HttpException) {
|
||||
Log.e("ERROR CODE", exception.code().toString())
|
||||
return
|
||||
}
|
||||
//Open the account page in a separate activity
|
||||
account.openProfile(context)
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<Account>,
|
||||
response: Response<Account>
|
||||
) {
|
||||
if(response.code() == 200) {
|
||||
val account = response.body()!!
|
||||
|
||||
//Open the account page in a separate activity
|
||||
account.openProfile(context)
|
||||
} else {
|
||||
Log.e("ERROR CODE", response.code().toString())
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,11 +77,6 @@ open class Status(
|
||||
fun getProfilePicUrl() : String? = account?.avatar
|
||||
fun getPostPreviewURL() : String? = media_attachments?.firstOrNull()?.preview_url
|
||||
|
||||
/**
|
||||
* @brief returns the parsed version of the HTML description
|
||||
*/
|
||||
private fun getDescription(api: PixelfedAPI, context: Context, credential: String) : Spanned =
|
||||
parseHTMLText(content ?: "", mentions, api, context, credential)
|
||||
|
||||
fun getNLikes(context: Context) : CharSequence {
|
||||
return context.getString(R.string.likes).format(favourites_count.toString())
|
||||
|
@ -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>
|
||||
@ -44,7 +45,7 @@
|
||||
<string name="request_format_error">Upload error: bad request format</string>
|
||||
<string name="upload_post_failed">Post upload failed</string>
|
||||
<string name="upload_post_success">Post uploaded successfully</string>
|
||||
<string name="upload_post_error">Post upload failed</string>
|
||||
<string name="upload_post_error">Post upload error</string>
|
||||
<string name="description">Description…</string>
|
||||
<string name="post">post</string>
|
||||
<string name="add_photo">Add a photo</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