Beginning of implementation of OAuth2 authentication mechanism.
This commit is contained in:
parent
63c8dbe09e
commit
0f7703be70
|
@ -47,6 +47,10 @@ android {
|
||||||
|
|
||||||
versionCode = androidGitVersion.code()
|
versionCode = androidGitVersion.code()
|
||||||
versionName = androidGitVersion.name()
|
versionName = androidGitVersion.name()
|
||||||
|
|
||||||
|
manifestPlaceholders = mapOf(
|
||||||
|
"appAuthRedirectScheme" to "urn"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
@ -133,6 +137,7 @@ dependencies {
|
||||||
implementation("com.google.android.exoplayer:exoplayer-ui:2.11.5")
|
implementation("com.google.android.exoplayer:exoplayer-ui:2.11.5")
|
||||||
implementation("com.google.android.exoplayer:extension-mediasession:2.11.5")
|
implementation("com.google.android.exoplayer:extension-mediasession:2.11.5")
|
||||||
|
|
||||||
|
implementation("com.github.openid:AppAuth-Android:27b62d5da9")
|
||||||
implementation("com.aliassadi:power-preference-lib:1.4.1")
|
implementation("com.aliassadi:power-preference-lib:1.4.1")
|
||||||
implementation("com.github.kittinunf.fuel:fuel:2.1.0")
|
implementation("com.github.kittinunf.fuel:fuel:2.1.0")
|
||||||
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.1.0")
|
implementation("com.github.kittinunf.fuel:fuel-coroutines:2.1.0")
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.doOnLayout
|
import androidx.core.view.doOnLayout
|
||||||
|
@ -12,12 +11,13 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import com.github.apognu.otter.R
|
import com.github.apognu.otter.R
|
||||||
import com.github.apognu.otter.fragments.LoginDialog
|
import com.github.apognu.otter.fragments.LoginDialog
|
||||||
import com.github.apognu.otter.utils.AppContext
|
import com.github.apognu.otter.utils.AppContext
|
||||||
|
import com.github.apognu.otter.utils.OAuth
|
||||||
import com.github.apognu.otter.utils.Userinfo
|
import com.github.apognu.otter.utils.Userinfo
|
||||||
|
import com.github.apognu.otter.utils.log
|
||||||
import com.github.kittinunf.fuel.Fuel
|
import com.github.kittinunf.fuel.Fuel
|
||||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
||||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||||
import com.github.kittinunf.result.Result
|
import com.github.kittinunf.result.Result
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.preference.PowerPreference
|
import com.preference.PowerPreference
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
import kotlinx.android.synthetic.main.activity_login.*
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
|
@ -37,20 +37,8 @@ class LoginActivity : AppCompatActivity() {
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
anonymous?.setOnCheckedChangeListener { _, isChecked ->
|
|
||||||
val state = when (isChecked) {
|
|
||||||
true -> View.GONE
|
|
||||||
false -> View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
username_field.visibility = state
|
|
||||||
password_field.visibility = state
|
|
||||||
}
|
|
||||||
|
|
||||||
login?.setOnClickListener {
|
login?.setOnClickListener {
|
||||||
var hostname = hostname.text.toString().trim()
|
var hostname = hostname.text.toString().trim()
|
||||||
val username = username.text.toString()
|
|
||||||
val password = password.text.toString()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (hostname.isEmpty()) throw Exception(getString(R.string.login_error_hostname))
|
if (hostname.isEmpty()) throw Exception(getString(R.string.login_error_hostname))
|
||||||
|
@ -71,7 +59,7 @@ class LoginActivity : AppCompatActivity() {
|
||||||
hostname_field.error = ""
|
hostname_field.error = ""
|
||||||
|
|
||||||
when (anonymous.isChecked) {
|
when (anonymous.isChecked) {
|
||||||
false -> authedLogin(hostname, username, password)
|
false -> authedLogin(hostname)
|
||||||
true -> anonymousLogin(hostname)
|
true -> anonymousLogin(hostname)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -90,64 +78,39 @@ class LoginActivity : AppCompatActivity() {
|
||||||
limitContainerWidth()
|
limitContainerWidth()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun authedLogin(hostname: String, username: String, password: String) {
|
private fun authedLogin(hostname: String) {
|
||||||
val body = mapOf(
|
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setString("hostname", hostname)
|
||||||
"username" to username,
|
|
||||||
"password" to password
|
|
||||||
).toList()
|
|
||||||
|
|
||||||
val dialog = LoginDialog().apply {
|
OAuth.init(hostname)
|
||||||
show(supportFragmentManager, "LoginDialog")
|
|
||||||
|
OAuth.register(this) {
|
||||||
|
OAuth.authorize(this)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lifecycleScope.launch(Main) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
try {
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
val (_, response, result) = Fuel.post("$hostname/api/v1/token/", body)
|
|
||||||
.awaitObjectResponseResult(gsonDeserializerOf(FwCredentials::class.java))
|
|
||||||
|
|
||||||
when (result) {
|
data?.let {
|
||||||
is Result.Success -> {
|
when (requestCode) {
|
||||||
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply {
|
0 -> {
|
||||||
setString("hostname", hostname)
|
OAuth.exchange(this, data,
|
||||||
setBoolean("anonymous", false)
|
{
|
||||||
setString("username", username)
|
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).setBoolean("anonymous", false)
|
||||||
setString("password", password)
|
|
||||||
setString("access_token", result.get().token)
|
|
||||||
}
|
|
||||||
|
|
||||||
Userinfo.get()?.let {
|
lifecycleScope.launch(Main) {
|
||||||
dialog.dismiss()
|
Userinfo.get(this@LoginActivity)?.let {
|
||||||
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
||||||
|
|
||||||
return@launch finish()
|
return@launch finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception(getString(R.string.login_error_userinfo))
|
throw Exception(getString(R.string.login_error_userinfo))
|
||||||
}
|
}
|
||||||
|
},
|
||||||
is Result.Failure -> {
|
{ "error".log() }
|
||||||
dialog.dismiss()
|
)
|
||||||
|
|
||||||
val error = Gson().fromJson(String(response.data), FwCredentials::class.java)
|
|
||||||
|
|
||||||
hostname_field.error = null
|
|
||||||
username_field.error = null
|
|
||||||
|
|
||||||
if (error != null && error.non_field_errors?.isNotEmpty() == true) {
|
|
||||||
username_field.error = error.non_field_errors[0]
|
|
||||||
} else {
|
|
||||||
hostname_field.error = result.error.localizedMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
dialog.dismiss()
|
|
||||||
|
|
||||||
val message =
|
|
||||||
if (e.message?.isEmpty() == true) getString(R.string.login_error_hostname)
|
|
||||||
else e.message
|
|
||||||
|
|
||||||
hostname_field.error = message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
CommandBus.send(Command.RefreshService)
|
CommandBus.send(Command.RefreshService)
|
||||||
|
|
||||||
lifecycleScope.launch(IO) {
|
lifecycleScope.launch(IO) {
|
||||||
Userinfo.get()
|
Userinfo.get(this@MainActivity)
|
||||||
}
|
}
|
||||||
|
|
||||||
now_playing_toggle.setOnClickListener {
|
now_playing_toggle.setOnClickListener {
|
||||||
|
@ -515,7 +515,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
try {
|
try {
|
||||||
Fuel
|
Fuel
|
||||||
.post(mustNormalizeUrl("/api/v1/history/listenings/"))
|
.post(mustNormalizeUrl("/api/v1/history/listenings/"))
|
||||||
.authorize()
|
.authorize(this@MainActivity)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(Gson().toJson(mapOf("track" to track.id)))
|
.body(Gson().toJson(mapOf("track" to track.id)))
|
||||||
.awaitStringResponse()
|
.awaitStringResponse()
|
||||||
|
|
|
@ -80,7 +80,7 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||||
|
|
||||||
val body = Gson().toJson(request)
|
val body = Gson().toJson(request)
|
||||||
val (_, response, result) = Fuel.post(mustNormalizeUrl("/api/v1/radios/sessions/"))
|
val (_, response, result) = Fuel.post(mustNormalizeUrl("/api/v1/radios/sessions/"))
|
||||||
.authorize()
|
.authorize(context)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(body)
|
.body(body)
|
||||||
.awaitObjectResponseResult(gsonDeserializerOf(RadioSession::class.java))
|
.awaitObjectResponseResult(gsonDeserializerOf(RadioSession::class.java))
|
||||||
|
@ -107,7 +107,7 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||||
try {
|
try {
|
||||||
val body = Gson().toJson(RadioTrackBody(session))
|
val body = Gson().toJson(RadioTrackBody(session))
|
||||||
val result = Fuel.post(mustNormalizeUrl("/api/v1/radios/tracks/"))
|
val result = Fuel.post(mustNormalizeUrl("/api/v1/radios/tracks/"))
|
||||||
.authorize()
|
.authorize(context)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.apply {
|
.apply {
|
||||||
cookie?.let {
|
cookie?.let {
|
||||||
|
@ -118,7 +118,7 @@ class RadioPlayer(val context: Context, val scope: CoroutineScope) {
|
||||||
.awaitObjectResult(gsonDeserializerOf(RadioTrack::class.java))
|
.awaitObjectResult(gsonDeserializerOf(RadioTrack::class.java))
|
||||||
|
|
||||||
val trackResponse = Fuel.get(mustNormalizeUrl("/api/v1/tracks/${result.get().track.id}/"))
|
val trackResponse = Fuel.get(mustNormalizeUrl("/api/v1/tracks/${result.get().track.id}/"))
|
||||||
.authorize()
|
.authorize(context)
|
||||||
.awaitObjectResult(gsonDeserializerOf(Track::class.java))
|
.awaitObjectResult(gsonDeserializerOf(Track::class.java))
|
||||||
|
|
||||||
val favorites = favoritedRepository.fetch(Repository.Origin.Cache.origin)
|
val favorites = favoritedRepository.fetch(Repository.Origin.Cache.origin)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.github.apognu.otter.repositories
|
package com.github.apognu.otter.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.github.apognu.otter.Otter
|
import com.github.apognu.otter.Otter
|
||||||
import com.github.apognu.otter.utils.*
|
import com.github.apognu.otter.utils.*
|
||||||
import com.github.kittinunf.fuel.Fuel
|
import com.github.kittinunf.fuel.Fuel
|
||||||
|
@ -11,7 +10,6 @@ import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.github.apognu.otter.utils
|
package com.github.apognu.otter.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.github.apognu.otter.R
|
import com.github.apognu.otter.R
|
||||||
|
@ -68,10 +69,12 @@ fun Picasso.maybeLoad(url: String?): RequestCreator {
|
||||||
else load(url)
|
else load(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Request.authorize(): Request {
|
fun Request.authorize(context: Context): Request {
|
||||||
return this.apply {
|
return this.apply {
|
||||||
if (!Settings.isAnonymous()) {
|
if (!Settings.isAnonymous()) {
|
||||||
header("Authorization", "Bearer ${Settings.getAccessToken()}")
|
OAuth.state().performActionWithFreshTokens(OAuth.service(context)) { token, _, _ ->
|
||||||
|
header("Authorization", "Bearer $token")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
package com.github.apognu.otter.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import com.github.kittinunf.fuel.Fuel
|
||||||
|
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
||||||
|
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||||
|
import com.github.kittinunf.fuel.gson.jsonBody
|
||||||
|
import com.github.kittinunf.result.Result
|
||||||
|
import com.preference.PowerPreference
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.openid.appauth.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
fun AuthState.save() {
|
||||||
|
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply {
|
||||||
|
setString("state", jsonSerializeString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object OAuth {
|
||||||
|
data class App(val client_id: String, val client_secret: String)
|
||||||
|
|
||||||
|
val REDIRECT_URI = Uri.parse("urn:/com.github.apognu.otter/oauth/callback")
|
||||||
|
|
||||||
|
fun state(): AuthState = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("state").run {
|
||||||
|
AuthState.jsonDeserialize(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(hostname: String) {
|
||||||
|
AuthState(config(hostname)).save()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun service(context: Context) = AuthorizationService(context)
|
||||||
|
|
||||||
|
fun register(context: Context, callback: () -> Unit) {
|
||||||
|
state().authorizationServiceConfiguration?.let { config ->
|
||||||
|
val body = mapOf(
|
||||||
|
"name" to UUID.randomUUID(),
|
||||||
|
"redirect_uris" to REDIRECT_URI.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
val (_, _, result) = Fuel.post(config.registrationEndpoint.toString())
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.jsonBody(body)
|
||||||
|
.awaitObjectResponseResult(gsonDeserializerOf(App::class.java))
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
is Result.Success -> {
|
||||||
|
val app = result.get()
|
||||||
|
|
||||||
|
val response = RegistrationResponse.Builder(registration()!!)
|
||||||
|
.setClientId(app.client_id)
|
||||||
|
.setClientSecret(app.client_secret)
|
||||||
|
.setClientIdIssuedAt(0)
|
||||||
|
.setClientSecretExpiresAt(null)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
state().apply {
|
||||||
|
update(response)
|
||||||
|
save()
|
||||||
|
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Result.Failure -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun authorize(context: Activity) {
|
||||||
|
val intent = service(context).run {
|
||||||
|
authorizationRequest()?.let {
|
||||||
|
getAuthorizationRequestIntent(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.startActivityForResult(intent, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exchange(context: Activity, authorization: Intent, success: () -> Unit, error: () -> Unit) {
|
||||||
|
state().let { state ->
|
||||||
|
state.apply {
|
||||||
|
update(AuthorizationResponse.fromIntent(authorization), AuthorizationException.fromIntent(authorization))
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationResponse.fromIntent(authorization)?.let {
|
||||||
|
val auth = ClientSecretPost(state().clientSecret)
|
||||||
|
|
||||||
|
service(context).performTokenRequest(it.createTokenExchangeRequest(), auth) { response, e ->
|
||||||
|
state
|
||||||
|
.apply {
|
||||||
|
update(response, e)
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null) success()
|
||||||
|
else error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun config(hostname: String) = AuthorizationServiceConfiguration(
|
||||||
|
Uri.parse("$hostname/authorize"),
|
||||||
|
Uri.parse("$hostname/api/v1/oauth/token/"),
|
||||||
|
Uri.parse("$hostname/api/v1/oauth/apps/")
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun registration() = state().authorizationServiceConfiguration?.let { config ->
|
||||||
|
RegistrationRequest.Builder(config, listOf(REDIRECT_URI)).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun authorizationRequest() = state().let { state ->
|
||||||
|
state.authorizationServiceConfiguration?.let { config ->
|
||||||
|
AuthorizationRequest.Builder(
|
||||||
|
config,
|
||||||
|
state.lastRegistrationResponse?.clientId ?: "",
|
||||||
|
ResponseTypeValues.CODE,
|
||||||
|
REDIRECT_URI
|
||||||
|
)
|
||||||
|
.setScopes(
|
||||||
|
/* "read:profile",
|
||||||
|
"read:libraries",
|
||||||
|
"write:libraries",
|
||||||
|
"read:favorites",
|
||||||
|
"write:favorites",
|
||||||
|
"read:playlists",
|
||||||
|
"write:playlists",
|
||||||
|
"read:radios",
|
||||||
|
"write:listenings" */
|
||||||
|
"read", "write"
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package com.github.apognu.otter.utils
|
package com.github.apognu.otter.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.github.kittinunf.fuel.Fuel
|
import com.github.kittinunf.fuel.Fuel
|
||||||
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
||||||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||||
|
@ -7,11 +8,11 @@ import com.github.kittinunf.result.Result
|
||||||
import com.preference.PowerPreference
|
import com.preference.PowerPreference
|
||||||
|
|
||||||
object Userinfo {
|
object Userinfo {
|
||||||
suspend fun get(): User? {
|
suspend fun get(context: Context): User? {
|
||||||
try {
|
try {
|
||||||
val hostname = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("hostname")
|
val hostname = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("hostname")
|
||||||
val (_, _, result) = Fuel.get("$hostname/api/v1/users/users/me/")
|
val (_, _, result) = Fuel.get("$hostname/api/v1/users/users/me/")
|
||||||
.authorize()
|
.authorize(context)
|
||||||
.awaitObjectResponseResult(gsonDeserializerOf(User::class.java))
|
.awaitObjectResponseResult(gsonDeserializerOf(User::class.java))
|
||||||
|
|
||||||
return when (result) {
|
return when (result) {
|
||||||
|
@ -28,6 +29,7 @@ object Userinfo {
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.google.android.exoplayer2.util.Log
|
import com.google.android.exoplayer2.util.Log
|
||||||
import com.preference.PowerPreference
|
import com.preference.PowerPreference
|
||||||
|
import net.openid.appauth.AuthState
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
fun Context?.toast(message: String, length: Int = Toast.LENGTH_SHORT) {
|
fun Context?.toast(message: String, length: Int = Toast.LENGTH_SHORT) {
|
||||||
|
@ -71,8 +72,8 @@ fun toDurationString(duration: Long, showSeconds: Boolean = false): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
object Settings {
|
object Settings {
|
||||||
fun hasAccessToken() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).contains("access_token")
|
fun hasAccessToken() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).contains("state") && OAuth.state().isAuthorized
|
||||||
fun getAccessToken(): String = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token", "")
|
fun getAccessToken() = OAuth.state().accessToken
|
||||||
fun isAnonymous() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getBoolean("anonymous", false)
|
fun isAnonymous() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getBoolean("anonymous", false)
|
||||||
fun areExperimentsEnabled() = PowerPreference.getDefaultFile().getBoolean("experiments", false)
|
fun areExperimentsEnabled() = PowerPreference.getDefaultFile().getBoolean("experiments", false)
|
||||||
fun getScope() = PowerPreference.getDefaultFile().getString("scope", "all")
|
fun getScope() = PowerPreference.getDefaultFile().getString("scope", "all")
|
||||||
|
|
|
@ -69,57 +69,6 @@
|
||||||
android:text="@string/login_anonymous"
|
android:text="@string/login_anonymous"
|
||||||
android:textColor="@android:color/white" />
|
android:textColor="@android:color/white" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/username_field"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:hint="@string/login_username"
|
|
||||||
android:textColorHint="@drawable/login_input"
|
|
||||||
app:boxBackgroundColor="@color/controlAccent"
|
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:boxStrokeColor="@drawable/login_input"
|
|
||||||
app:boxStrokeWidth="0dp"
|
|
||||||
app:hintTextColor="@drawable/login_input">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/username"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textEmailAddress"
|
|
||||||
android:lines="1"
|
|
||||||
android:text="@string/debug.username"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textCursorDrawable="@null" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/password_field"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:hint="@string/login_password"
|
|
||||||
android:textColorHint="@drawable/login_input"
|
|
||||||
app:boxBackgroundColor="@color/controlAccent"
|
|
||||||
app:boxBackgroundMode="filled"
|
|
||||||
app:boxStrokeColor="@drawable/login_input"
|
|
||||||
app:boxStrokeWidth="0dp"
|
|
||||||
app:hintTextColor="@drawable/login_input"
|
|
||||||
app:passwordToggleEnabled="true">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:lines="1"
|
|
||||||
android:text="@string/debug.password"
|
|
||||||
android:textColor="@android:color/white"
|
|
||||||
android:textCursorDrawable="@null" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/login"
|
android:id="@+id/login"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
Loading…
Reference in New Issue