Allow for anonymous connection if server supports it. Should provide basic support for #14.
This commit is contained in:
parent
3101fa5302
commit
aad0ec439c
|
@ -3,6 +3,7 @@ package com.github.apognu.otter.activities
|
|||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.LoginDialog
|
||||
|
@ -30,6 +31,16 @@ class LoginActivity : AppCompatActivity() {
|
|||
override fun 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 {
|
||||
var hostname = hostname.text.toString().trim()
|
||||
val username = username.text.toString()
|
||||
|
@ -55,58 +66,106 @@ class LoginActivity : AppCompatActivity() {
|
|||
|
||||
hostname_field.error = ""
|
||||
|
||||
val body = mapOf(
|
||||
"username" to username,
|
||||
"password" to password
|
||||
).toList()
|
||||
|
||||
val dialog = LoginDialog().apply {
|
||||
show(supportFragmentManager, "LoginDialog")
|
||||
when (anonymous.isChecked) {
|
||||
false -> authedLogin(hostname, username, password)
|
||||
true -> anonymousLogin(hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(Main) {
|
||||
try {
|
||||
val (_, response, result) = Fuel.post("$hostname/api/v1/token/", body)
|
||||
.awaitObjectResponseResult(gsonDeserializerOf(FwCredentials::class.java))
|
||||
private fun authedLogin(hostname: String, username: String, password: String) {
|
||||
val body = mapOf(
|
||||
"username" to username,
|
||||
"password" to password
|
||||
).toList()
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply {
|
||||
setString("hostname", hostname)
|
||||
setString("username", username)
|
||||
setString("password", password)
|
||||
setString("access_token", result.get().token)
|
||||
}
|
||||
val dialog = LoginDialog().apply {
|
||||
show(supportFragmentManager, "LoginDialog")
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
||||
finish()
|
||||
GlobalScope.launch(Main) {
|
||||
try {
|
||||
val (_, response, result) = Fuel.post("$hostname/api/v1/token/", body)
|
||||
.awaitObjectResponseResult(gsonDeserializerOf(FwCredentials::class.java))
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply {
|
||||
setString("hostname", hostname)
|
||||
setBoolean("anonymous", false)
|
||||
setString("username", username)
|
||||
setString("password", password)
|
||||
setString("access_token", result.get().token)
|
||||
}
|
||||
|
||||
is Result.Failure -> {
|
||||
dialog.dismiss()
|
||||
dialog.dismiss()
|
||||
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
val error = Gson().fromJson(String(response.data), FwCredentials::class.java)
|
||||
is Result.Failure -> {
|
||||
dialog.dismiss()
|
||||
|
||||
hostname_field.error = null
|
||||
username_field.error = null
|
||||
val error = Gson().fromJson(String(response.data), FwCredentials::class.java)
|
||||
|
||||
if (error != null && error.non_field_errors.isNotEmpty()) {
|
||||
username_field.error = error.non_field_errors[0]
|
||||
} else {
|
||||
hostname_field.error = result.error.localizedMessage
|
||||
}
|
||||
hostname_field.error = null
|
||||
username_field.error = null
|
||||
|
||||
if (error != null && error.non_field_errors.isNotEmpty()) {
|
||||
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
|
||||
}
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun anonymousLogin(hostname: String) {
|
||||
val dialog = LoginDialog().apply {
|
||||
show(supportFragmentManager, "LoginDialog")
|
||||
}
|
||||
|
||||
GlobalScope.launch(Main) {
|
||||
try {
|
||||
val (_, _, result) = Fuel.get("$hostname/api/v1/tracks/")
|
||||
.awaitObjectResponseResult(gsonDeserializerOf(FwCredentials::class.java))
|
||||
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply {
|
||||
setString("hostname", hostname)
|
||||
setBoolean("anonymous", true)
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
startActivity(Intent(this@LoginActivity, MainActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
|
||||
is Result.Failure -> {
|
||||
dialog.dismiss()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,14 @@ import android.content.Intent
|
|||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.Settings
|
||||
|
||||
class SplashActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
getSharedPreferences(AppContext.PREFS_CREDENTIALS, Context.MODE_PRIVATE).apply {
|
||||
when (contains("access_token")) {
|
||||
when (Settings.hasAccessToken() || Settings.isAnonymous()) {
|
||||
true -> Intent(this@SplashActivity, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NO_ANIMATION
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.github.apognu.otter.repositories.HttpUpstream
|
||||
import com.github.apognu.otter.repositories.Repository
|
||||
import com.github.apognu.otter.utils.Cache
|
||||
import com.github.apognu.otter.utils.log
|
||||
import com.github.apognu.otter.utils.untilNetwork
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.android.synthetic.main.fragment_artists.*
|
||||
|
|
|
@ -57,11 +57,11 @@ class QueueManager(val context: Context) {
|
|||
}
|
||||
|
||||
private fun factory(): CacheDataSourceFactory {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
|
||||
val http = DefaultHttpDataSourceFactory(Util.getUserAgent(context, context.getString(R.string.app_name))).apply {
|
||||
defaultRequestProperties.apply {
|
||||
set("Authorization", "Bearer $token")
|
||||
if (!Settings.isAnonymous()) {
|
||||
set("Authorization", "Bearer ${Settings.getAccessToken()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import com.github.kittinunf.fuel.coroutines.awaitByteArrayResponseResult
|
|||
import com.github.kittinunf.fuel.gson.gsonDeserializerOf
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.preference.PowerPreference
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -26,13 +25,16 @@ class FavoritesRepository(override val context: Context?) : Repository<Track, Tr
|
|||
}
|
||||
|
||||
fun addFavorite(id: Int) {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
val body = mapOf("track" to id)
|
||||
|
||||
val request = Fuel.post(mustNormalizeUrl("/api/v1/favorites/tracks/")).apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
header("Authorization", "Bearer ${Settings.getAccessToken()}")
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(IO) {
|
||||
Fuel
|
||||
.post(mustNormalizeUrl("/api/v1/favorites/tracks/"))
|
||||
.header("Authorization", "Bearer $token")
|
||||
request
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Gson().toJson(body))
|
||||
.awaitByteArrayResponseResult()
|
||||
|
@ -40,13 +42,16 @@ class FavoritesRepository(override val context: Context?) : Repository<Track, Tr
|
|||
}
|
||||
|
||||
fun deleteFavorite(id: Int) {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
val body = mapOf("track" to id)
|
||||
|
||||
val request = Fuel.post(mustNormalizeUrl("/api/v1/favorites/tracks/remove/")).apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
request.header("Authorization", "Bearer ${Settings.getAccessToken()}")
|
||||
}
|
||||
}
|
||||
|
||||
GlobalScope.launch(IO) {
|
||||
Fuel
|
||||
.post(mustNormalizeUrl("/api/v1/favorites/tracks/remove/"))
|
||||
.header("Authorization", "Bearer $token")
|
||||
request
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Gson().toJson(body))
|
||||
.awaitByteArrayResponseResult()
|
||||
|
|
|
@ -9,7 +9,6 @@ import com.github.kittinunf.fuel.coroutines.awaitObjectResponseResult
|
|||
import com.github.kittinunf.fuel.coroutines.awaitObjectResult
|
||||
import com.github.kittinunf.result.Result
|
||||
import com.google.gson.Gson
|
||||
import com.preference.PowerPreference
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
@ -37,6 +36,8 @@ class HttpUpstream<D : Any, R : FunkwhaleResponse<D>>(val behavior: Behavior, pr
|
|||
.build()
|
||||
.toString()
|
||||
|
||||
log(offsetUrl)
|
||||
|
||||
get(offsetUrl).fold(
|
||||
{ response ->
|
||||
val data = response.getData()
|
||||
|
@ -65,12 +66,13 @@ class HttpUpstream<D : Any, R : FunkwhaleResponse<D>>(val behavior: Behavior, pr
|
|||
}
|
||||
|
||||
suspend fun get(url: String): Result<R, FuelError> {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
val request = Fuel.get(mustNormalizeUrl(url)).apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
header("Authorization", "Bearer ${Settings.isAnonymous()}")
|
||||
}
|
||||
}
|
||||
|
||||
val (_, response, result) = Fuel
|
||||
.get(mustNormalizeUrl(url))
|
||||
.header("Authorization", "Bearer $token")
|
||||
.awaitObjectResponseResult(GenericDeserializer<R>(type))
|
||||
val (_, response, result) = request.awaitObjectResponseResult(GenericDeserializer<R>(type))
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
return retryGet(url)
|
||||
|
@ -81,12 +83,13 @@ class HttpUpstream<D : Any, R : FunkwhaleResponse<D>>(val behavior: Behavior, pr
|
|||
|
||||
private suspend fun retryGet(url: String): Result<R, FuelError> {
|
||||
return if (HTTP.refresh()) {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
val request = Fuel.get(mustNormalizeUrl(url)).apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
header("Authorization", "Bearer ${Settings.isAnonymous()}")
|
||||
}
|
||||
}
|
||||
|
||||
Fuel
|
||||
.get(mustNormalizeUrl(url))
|
||||
.header("Authorization", "Bearer $token")
|
||||
.awaitObjectResult(GenericDeserializer(type))
|
||||
request.awaitObjectResult(GenericDeserializer(type))
|
||||
} else {
|
||||
Result.Failure(FuelError.wrap(RefreshError))
|
||||
}
|
||||
|
|
|
@ -36,12 +36,13 @@ object HTTP {
|
|||
}
|
||||
|
||||
suspend inline fun <reified T : Any> get(url: String): Result<T, FuelError> {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
val request = Fuel.get(mustNormalizeUrl(url)).apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
header("Authorization", "Bearer ${Settings.getAccessToken()}")
|
||||
}
|
||||
}
|
||||
|
||||
val (_, response, result) = Fuel
|
||||
.get(mustNormalizeUrl(url))
|
||||
.header("Authorization", "Bearer $token")
|
||||
.awaitObjectResponseResult(gsonDeserializerOf(T::class.java))
|
||||
val (_, response, result) = request.awaitObjectResponseResult(gsonDeserializerOf(T::class.java))
|
||||
|
||||
if (response.statusCode == 401) {
|
||||
return retryGet(url)
|
||||
|
@ -52,12 +53,13 @@ object HTTP {
|
|||
|
||||
suspend inline fun <reified T : Any> retryGet(url: String): Result<T, FuelError> {
|
||||
return if (refresh()) {
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token")
|
||||
val request = Fuel.get(mustNormalizeUrl(url)).apply {
|
||||
if (!Settings.isAnonymous()) {
|
||||
header("Authorization", "Bearer ${Settings.getAccessToken()}")
|
||||
}
|
||||
}
|
||||
|
||||
Fuel
|
||||
.get(mustNormalizeUrl(url))
|
||||
.header("Authorization", "Bearer $token")
|
||||
.awaitObjectResult(gsonDeserializerOf(T::class.java))
|
||||
request.awaitObjectResult(gsonDeserializerOf(T::class.java))
|
||||
} else {
|
||||
Result.Failure(FuelError.wrap(RefreshError))
|
||||
}
|
||||
|
|
|
@ -48,4 +48,10 @@ fun toDurationString(seconds: Long): String {
|
|||
if (minutes > 0) ret.append(" ${minutes}m")
|
||||
|
||||
return ret.toString()
|
||||
}
|
||||
|
||||
object Settings {
|
||||
fun hasAccessToken() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).contains("access_token")
|
||||
fun getAccessToken() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("access_token", "")
|
||||
fun isAnonymous() = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getBoolean("anonymous", false)
|
||||
}
|
|
@ -49,6 +49,14 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/anonymous"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_anonymous"
|
||||
android:buttonTint="@android:color/white"
|
||||
android:textColor="@android:color/white"/>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/username_field"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<string name="login_welcome">Veuillez saisir les détails de votre instance Funkwhale pour accéder à son contenu</string>
|
||||
<string name="login_hostname">Nom d\'hôte</string>
|
||||
<string name="login_anonymous">Authentification anonyme</string>
|
||||
<string name="login_username">Nom d\'utilisateur</string>
|
||||
<string name="login_password">Mot de passe</string>
|
||||
<string name="login_submit">Se connecter</string>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<string name="login_welcome">Please enter the details of your Funkwhale instance to access its content</string>
|
||||
<string name="login_hostname">Host name</string>
|
||||
<string name="login_anonymous">Anonymous authentication</string>
|
||||
<string name="login_username">Username</string>
|
||||
<string name="login_password">Password</string>
|
||||
<string name="login_submit">Log in</string>
|
||||
|
|
Loading…
Reference in New Issue