From aad0ec439ccb73365b7ec1d07ee940a97bd8cc4b Mon Sep 17 00:00:00 2001 From: Antoine POPINEAU Date: Mon, 25 Nov 2019 23:14:16 +0100 Subject: [PATCH] Allow for anonymous connection if server supports it. Should provide basic support for #14. --- .../apognu/otter/activities/LoginActivity.kt | 139 +++++++++++++----- .../apognu/otter/activities/SplashActivity.kt | 3 +- .../otter/fragments/FunkwhaleFragment.kt | 1 - .../apognu/otter/playback/QueueManager.kt | 6 +- .../otter/repositories/FavoritesRepository.kt | 23 +-- .../apognu/otter/repositories/HttpUpstream.kt | 25 ++-- .../com/github/apognu/otter/utils/Data.kt | 22 +-- .../com/github/apognu/otter/utils/Util.kt | 6 + app/src/main/res/layout/activity_login.xml | 8 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 11 files changed, 160 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt index 8f1bee6..682d128 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/LoginActivity.kt @@ -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 } } } diff --git a/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt index 7c6b30a..fe617f1 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/SplashActivity.kt @@ -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 diff --git a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt index ff358b3..f12b7b9 100644 --- a/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt +++ b/app/src/main/java/com/github/apognu/otter/fragments/FunkwhaleFragment.kt @@ -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.* diff --git a/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt b/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt index f4ab4c1..503863c 100644 --- a/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt +++ b/app/src/main/java/com/github/apognu/otter/playback/QueueManager.kt @@ -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()}") + } } } diff --git a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt index 8194091..6a13295 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/FavoritesRepository.kt @@ -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>(val behavior: Behavior, pr .build() .toString() + log(offsetUrl) + get(offsetUrl).fold( { response -> val data = response.getData() @@ -65,12 +66,13 @@ class HttpUpstream>(val behavior: Behavior, pr } suspend fun get(url: String): Result { - 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(type)) + val (_, response, result) = request.awaitObjectResponseResult(GenericDeserializer(type)) if (response.statusCode == 401) { return retryGet(url) @@ -81,12 +83,13 @@ class HttpUpstream>(val behavior: Behavior, pr private suspend fun retryGet(url: String): Result { 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)) } diff --git a/app/src/main/java/com/github/apognu/otter/utils/Data.kt b/app/src/main/java/com/github/apognu/otter/utils/Data.kt index 08805b0..9210464 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/Data.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/Data.kt @@ -36,12 +36,13 @@ object HTTP { } suspend inline fun get(url: String): Result { - 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 retryGet(url: String): Result { 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)) } diff --git a/app/src/main/java/com/github/apognu/otter/utils/Util.kt b/app/src/main/java/com/github/apognu/otter/utils/Util.kt index 2dca96c..1e262df 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/Util.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/Util.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 6d405ad..2209e8d 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -49,6 +49,14 @@ + + Veuillez saisir les détails de votre instance Funkwhale pour accéder à son contenu Nom d\'hôte + Authentification anonyme Nom d\'utilisateur Mot de passe Se connecter diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7785003..8874420 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,7 @@ Please enter the details of your Funkwhale instance to access its content Host name + Anonymous authentication Username Password Log in